elwm / yt_clipper

(function () {
function $parcel$interopDefault(a) {
  return a && a.__esModule ? {
    d: a.default
  } : {
    d: a
  };
}

var $parcel$global = this;
// ASSET: yt_clipper.ts
var $XbmT$exports = {};
// ASSET: ..\..\node_modules\file-saver\dist\FileSaver.min.js
var $oSxG$exports = {};
var $oSxG$var$define;

(function (a, b) {
  if ("function" == typeof $oSxG$var$define && $oSxG$var$define.amd) $oSxG$var$define([], b);else if ("undefined" != typeof $oSxG$exports) b();else {
    b(), a.FileSaver = {
      exports: {}
    }.exports;
  }
})($oSxG$exports, function () {
  function b(a, b) {
    return "undefined" == typeof b ? b = {
      autoBom: !1
    } : "object" != typeof b && (console.warn("Deprecated: Expected third argument to be a object"), b = {
      autoBom: !b
    }), b.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type) ? new Blob(["\uFEFF", a], {
      type: a.type
    }) : a;
  }

  function c(b, c, d) {
    var e = new XMLHttpRequest();
    e.open("GET", b), e.responseType = "blob", e.onload = function () {
      a(e.response, c, d);
    }, e.onerror = function () {
      console.error("could not download file");
    }, e.send();
  }

  function d(a) {
    var b = new XMLHttpRequest();
    b.open("HEAD", a, !1);

    try {
      b.send();
    } catch (a) {}

    return 200 <= b.status && 299 >= b.status;
  }

  function e(a) {
    try {
      a.dispatchEvent(new MouseEvent("click"));
    } catch (c) {
      var b = document.createEvent("MouseEvents");
      b.initMouseEvent("click", !0, !0, window, 0, 0, 0, 80, 20, !1, !1, !1, !1, 0, null), a.dispatchEvent(b);
    }
  }

  var f = "object" == typeof window && window.window === window ? window : "object" == typeof self && self.self === self ? self : "object" == typeof $parcel$global && $parcel$global.global === $parcel$global ? $parcel$global : void 0,
      a = f.saveAs || ("object" != typeof window || window !== f ? function () {} : "download" in HTMLAnchorElement.prototype ? function (b, g, h) {
    var i = f.URL || f.webkitURL,
        j = document.createElement("a");
    g = g || b.name || "download", j.download = g, j.rel = "noopener", "string" == typeof b ? (j.href = b, j.origin === location.origin ? e(j) : d(j.href) ? c(b, g, h) : e(j, j.target = "_blank")) : (j.href = i.createObjectURL(b), setTimeout(function () {
      i.revokeObjectURL(j.href);
    }, 4E4), setTimeout(function () {
      e(j);
    }, 0));
  } : "msSaveOrOpenBlob" in navigator ? function (f, g, h) {
    if (g = g || f.name || "download", "string" != typeof f) navigator.msSaveOrOpenBlob(b(f, h), g);else if (d(f)) c(f, g, h);else {
      var i = document.createElement("a");
      i.href = f, i.target = "_blank", setTimeout(function () {
        e(i);
      });
    }
  } : function (a, b, d, e) {
    if (e = e || open("", "_blank"), e && (e.document.title = e.document.body.innerText = "downloading..."), "string" == typeof a) return c(a, b, d);
    var g = "application/octet-stream" === a.type,
        h = /constructor/i.test(f.HTMLElement) || f.safari,
        i = /CriOS\/[\d]+/.test(navigator.userAgent);

    if ((i || g && h) && "object" == typeof FileReader) {
      var j = new FileReader();
      j.onloadend = function () {
        var a = j.result;
        a = i ? a : a.replace(/^data:[^;]*;/, "data:attachment/file;"), e ? e.location.href = a : location = a, e = null;
      }, j.readAsDataURL(a);
    } else {
      var k = f.URL || f.webkitURL,
          l = k.createObjectURL(a);
      e ? e.location = l : location.href = l, e = null, setTimeout(function () {
        k.revokeObjectURL(l);
      }, 4E4);
    }
  });
  f.saveAs = a.saveAs = a, "undefined" != "object" && ($oSxG$exports = a);
}); //# sourceMappingURL=FileSaver.min.js.map


// ASSET: ..\..\.parcel-globals\jszip.js
var $ZBU$exports = {};

if ("production" === 'production') {
  $ZBU$exports = JSZip;
} else {
  $ZBU$exports = JSZip;
}

// ASSET: ..\..\.parcel-globals\chart.js.js
var $JsLj$exports = {};

if ("production" === 'production') {
  $JsLj$exports = Chart;
} else {
  $JsLj$exports = Chart;
}

async function $BHXf$export$retryUntilTruthyResult(fn) {
  let wait = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100;
  let result = fn();

  while (!result) {
    console.log(`Retrying function: ${fn.name} because result was ${result}`);
    result = fn();
    await $BHXf$export$sleep(wait);
  }

  return result;
}

function $BHXf$export$sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function $BHXf$export$htmlToElement(html) {
  const template = document.createElement('template');
  html = html.trim(); // Never return a text node of whitespace as the result

  template.innerHTML = html;
  return template.content.firstChild;
}

function $BHXf$export$htmlToSVGElement(html) {
  const template = document.createElementNS('http://www.w3.org/2000/svg', 'template');
  html = html.trim(); // Never return a text node of whitespace as the result

  template.innerHTML = html;
  return template.firstElementChild;
}

function $BHXf$export$deleteElement(elem) {
  if (elem && elem.parentElement) {
    elem.parentElement.removeChild(elem);
  }
}

function $BHXf$export$once(fn, context) {
  var result;
  return function () {
    if (fn) {
      result = fn.apply(context || this, arguments);
      fn = null;
    }

    return result;
  };
}

function $BHXf$export$setAttributes(el, attrs) {
  Object.keys(attrs).forEach(key => el.setAttribute(key, attrs[key]));
}

function $BHXf$export$copyToClipboard(str) {
  const el = document.createElement('textarea');
  el.value = str;
  document.body.appendChild(el);
  el.select();
  document.execCommand('copy');
  document.body.removeChild(el);
}

function $BHXf$export$createRounder(multiple, precision) {
  return value => {
    const roundedValue = Math.round(value / multiple) * multiple;
    const roundedValueFixedPrecision = +roundedValue.toFixed(precision);
    return roundedValueFixedPrecision;
  };
}

function $BHXf$export$roundValue(value, multiple, precision) {
  return $BHXf$export$createRounder(multiple, precision)(value);
}

function $BHXf$export$clampNumber(number, min, max) {
  return Math.max(min, Math.min(number, max));
}

function $BHXf$export$toHHMMSS(seconds) {
  return new Date(seconds * 1000).toISOString().substr(11, 12);
}

function $BHXf$export$toHHMMSSTrimmed(seconds) {
  return $BHXf$export$toHHMMSS(seconds).replace(/(00:)+(.*)/, '$2');
}

const $ZMrW$var$sortX = (a, b) => {
  if (a.x < b.x) return -1;
  if (a.x > b.x) return 1;
  return 0;
};

const $ZMrW$var$lightgrey = opacity => `rgba(120, 120, 120, ${opacity})`;

const $ZMrW$var$medgrey = opacity => `rgba(90, 90, 90, ${opacity})`;

const $ZMrW$var$grey = opacity => `rgba(50, 50, 50, ${opacity})`;

const $ZMrW$export$speedPointRawSecondsFormatter = point => {
  return `T:${point.x.toFixed(2)}\nS:${+point.y.toFixed(2)}`;
};

const $ZMrW$export$cubicInOutTension = 0.6;
const $ZMrW$export$global = {
  defaultColor: 'rgba(255, 255, 255, 1)',
  defaultFontSize: 16,
  defaultFontStyle: 'bold',
  defaultFontColor: $ZMrW$var$lightgrey(1),
  maintainAspectRatio: false,
  hover: {
    mode: 'nearest'
  },
  animation: {
    duration: 0
  }
};
const $ZMrW$var$roundX = $BHXf$export$createRounder(0.05, 2);
const $ZMrW$var$roundY = $BHXf$export$createRounder(0.05, 2);

function $ZMrW$var$getSpeedPointColor(context) {
  var index = context.dataIndex;
  var value = context.dataset.data[index];
  return value.y <= 1 ? `rgba(255, ${100 * value.y}, 100, 0.9)` : `rgba(${130 - 90 * (value.y - 1)}, 100, 245, 0.9)`;
}

function $ZMrW$var$updateSpeedInput(newSpeed) {
  const speedInput = document.getElementById('speed-input');

  if (speedInput) {
    if (newSpeed) speedInput.value = newSpeed.toString();
    speedInput.dispatchEvent(new Event('change'));
  }
}

function $ZMrW$var$getSpeedChartBounds(chartInstance) {
  const speedChartBounds = {
    XMinBound: chartInstance.options.scales.xAxes[0].ticks.min,
    XMaxBound: chartInstance.options.scales.xAxes[0].ticks.max,
    YMinBound: 0.05,
    YMaxBound: 2
  };
  return speedChartBounds;
}

const $ZMrW$export$options = {
  type: 'scatter',
  data: {
    datasets: [{
      label: 'Speed',
      lineTension: 0,
      data: [],
      showLine: true,
      pointBackgroundColor: $ZMrW$var$getSpeedPointColor,
      pointBorderColor: $ZMrW$var$medgrey(0.7),
      pointRadius: 5,
      pointHoverRadius: 4,
      pointHoverBorderWidth: 1.5,
      pointHoverBorderColor: $ZMrW$var$lightgrey(0.8),
      pointHitRadius: 4
    }]
  },
  options: {
    elements: {
      line: {
        fill: true,
        backgroundColor: 'rgba(160,0, 255, 0.1)',
        borderColor: $ZMrW$var$lightgrey(0.8),
        borderWidth: 2,
        borderDash: [5, 2]
      }
    },
    legend: {
      display: false
    },
    layout: {
      padding: {
        left: 0,
        right: 0,
        top: 15,
        bottom: 0
      }
    },
    tooltips: {
      enabled: false
    },
    scales: {
      xAxes: [{
        scaleLabel: {
          display: true,
          labelString: 'Time (s)',
          fontSize: 12,
          padding: 0
        },
        position: 'bottom',
        gridLines: {
          color: $ZMrW$var$medgrey(0.6),
          lineWidth: 1
        },
        ticks: {
          min: 0,
          max: 10,
          maxTicksLimit: 100,
          autoSkip: false,
          maxRotation: 0,
          minRotation: 0,
          major: {
            fontColor: 'red'
          },
          minor: {}
        }
      }],
      yAxes: [{
        scaleLabel: {
          display: true,
          labelString: 'Speed',
          fontSize: 12,
          padding: 0
        },
        gridLines: {
          color: $ZMrW$var$medgrey(0.6),
          lineWidth: 1
        },
        ticks: {
          stepSize: 0.1,
          min: 0,
          max: 2
        }
      }]
    },
    plugins: {
      datalabels: {
        clip: false,
        clamp: true,
        font: {
          size: 14,
          weight: 'bold'
        },
        textStrokeWidth: 2,
        textStrokeColor: $ZMrW$var$grey(0.9),
        textAlign: 'center',
        formatter: $ZMrW$export$speedPointRawSecondsFormatter,
        display: function display(context) {
          return context.active ? true : 'auto';
        },
        align: function align(context) {
          const index = context.dataIndex; // const value = context.dataset.data[index];

          if (index === 0) {
            return 'right';
          } else if (index === context.dataset.data.length - 1) {
            return 'left';
          } else if (context.dataset.data[context.dataIndex].y > 1.85) {
            return 'start';
          } else {
            return 'end';
          }
        },
        color: $ZMrW$var$getSpeedPointColor
      },
      zoom: {
        pan: {
          enabled: true,
          mode: 'x',
          rangeMin: {
            x: 0,
            y: 0
          },
          rangeMax: {
            x: 10,
            y: 2
          }
        },
        zoom: {
          enabled: true,
          mode: 'x',
          drag: false,
          speed: 0.1,
          rangeMin: {
            x: 0,
            y: 0
          },
          rangeMax: {
            x: 10,
            y: 2
          }
        }
      }
    },
    annotation: {
      drawTime: 'afterDraw',
      annotations: [{
        label: 'time',
        type: 'line',
        mode: 'vertical',
        scaleID: 'x-axis-1',
        value: 2,
        borderColor: 'rgba(255, 0, 0, 0.9)',
        borderWidth: 1
      }, {
        label: 'start',
        type: 'line',
        display: true,
        mode: 'vertical',
        scaleID: 'x-axis-1',
        value: -1,
        borderColor: 'rgba(0, 255, 0, 0.9)',
        borderWidth: 1
      }, {
        label: 'end',
        type: 'line',
        display: true,
        mode: 'vertical',
        scaleID: 'x-axis-1',
        value: -1,
        borderColor: 'rgba(255, 215, 0, 0.9)',
        borderWidth: 1
      }]
    },
    onHover: (event, chartElement) => {
      event.target.style.cursor = chartElement[0] ? 'grab' : 'default';
    },
    dragData: true,
    dragY: true,
    dragX: true,
    dragDataRound: 0.5,
    dragDataRoundMultipleX: 0.05,
    dragDataRoundPrecisionX: 2,
    dragDataRoundMultipleY: 0.05,
    dragDataRoundPrecisionY: 2,
    dragDataSort: false,
    dragDataSortFunction: $ZMrW$var$sortX,
    onDragStart: function onDragStart(e, chartInstance, element) {
      // console.log(e, element);
      chartInstance.options.plugins.zoom.pan.enabled = false;
      event.target.style.cursor = 'grabbing';
      chartInstance.update();
    },
    onDrag: function onDrag(e, chartInstance, datasetIndex, index, fromValue, toValue) {
      // console.log(datasetIndex, index, fromValue, toValue);
      const shouldDrag = {
        dragX: true,
        dragY: true
      };
      const speedChartBounds = $ZMrW$var$getSpeedChartBounds(chartInstance);

      if (fromValue.x <= speedChartBounds.XMinBound || fromValue.x >= speedChartBounds.XMaxBound || toValue.x <= speedChartBounds.XMinBound || toValue.x >= speedChartBounds.XMaxBound) {
        shouldDrag.dragX = false;
      }

      if (toValue.y < speedChartBounds.YMinBound || toValue.y > speedChartBounds.YMaxBound) {
        shouldDrag.dragY = false;
      }

      return shouldDrag;
    },
    onDragEnd: function onDragEnd(e, chartInstance, datasetIndex, index, value) {
      // console.log(datasetIndex, index, value);
      if (index === 0) {
        $ZMrW$var$updateSpeedInput(value.y);
      } else {
        $ZMrW$var$updateSpeedInput();
      }

      chartInstance.data.datasets[datasetIndex].data.sort($ZMrW$var$sortX);
      chartInstance.options.plugins.zoom.pan.enabled = true;
      event.target.style.cursor = 'default';
      chartInstance.update({
        duration: 0
      });
    },
    onClick: function onClick(event, dataAtClick) {
      if (event.button === 0 && !event.ctrlKey && !event.altKey && event.shiftKey && dataAtClick.length === 0) {
        // console.log(element, dataAtClick);
        let valueX, valueY;
        valueX = this.scales['x-axis-1'].getValueForPixel(event.offsetX);
        valueY = this.scales['y-axis-1'].getValueForPixel(event.offsetY);

        if (valueX && valueY) {
          const speedChartBounds = $ZMrW$var$getSpeedChartBounds(this);

          if (valueX <= speedChartBounds.XMinBound || valueX >= speedChartBounds.XMaxBound || valueY < speedChartBounds.YMinBound || valueY > speedChartBounds.YMaxBound) {
            return;
          }

          valueX = $ZMrW$var$roundX(valueX);
          valueY = $ZMrW$var$roundY(valueY);
          this.data.datasets[0].data.push({
            x: valueX,
            y: valueY
          });
          this.data.datasets[0].data.sort($ZMrW$var$sortX);
          $ZMrW$var$updateSpeedInput();
          this.update();
        }
      }

      if (event.button === 0 && !event.ctrlKey && event.altKey && event.shiftKey && dataAtClick.length === 1) {
        const datum = dataAtClick[0];

        if (datum) {
          const datasetIndex = datum['_datasetIndex'];
          const index = datum['_index'];
          let speedChartMinBound = this.options.scales.xAxes[0].ticks.min;
          let speedChartMaxBound = this.options.scales.xAxes[0].ticks.max;
          let dataRef = this.data.datasets[datasetIndex].data;

          if (dataRef[index].x !== speedChartMinBound && dataRef[index].x !== speedChartMaxBound) {
            dataRef.splice(index, 1);
            $ZMrW$var$updateSpeedInput();
            this.update();
          }
        }
      }

      if (event.ctrlKey && !event.altKey && !event.shiftKey) {
        this.resetZoom();
      }
    }
  }
};
var $SYAW$var$noop = {
  value: function () {}
};

function $SYAW$export$default() {
  for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
    if (!(t = arguments[i] + "") || t in _) throw new Error("illegal type: " + t);
    _[t] = [];
  }

  return new $SYAW$var$Dispatch(_);
}

function $SYAW$var$Dispatch(_) {
  this._ = _;
}

function $SYAW$var$parseTypenames(typenames, types) {
  return typenames.trim().split(/^|\s+/).map(function (t) {
    var name = "",
        i = t.indexOf(".");
    if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
    if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t);
    return {
      type: t,
      name: name
    };
  });
}

$SYAW$var$Dispatch.prototype = $SYAW$export$default.prototype = {
  constructor: $SYAW$var$Dispatch,
  on: function (typename, callback) {
    var _ = this._,
        T = $SYAW$var$parseTypenames(typename + "", _),
        t,
        i = -1,
        n = T.length; // If no callback was specified, return the callback of the given type and name.

    if (arguments.length < 2) {
      while (++i < n) if ((t = (typename = T[i]).type) && (t = $SYAW$var$get(_[t], typename.name))) return t;

      return;
    } // If a type was specified, set the callback for the given type and name.
    // Otherwise, if a null callback was specified, remove callbacks of the given name.


    if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback);

    while (++i < n) {
      if (t = (typename = T[i]).type) _[t] = $SYAW$var$set(_[t], typename.name, callback);else if (callback == null) for (t in _) _[t] = $SYAW$var$set(_[t], typename.name, null);
    }

    return this;
  },
  copy: function () {
    var copy = {},
        _ = this._;

    for (var t in _) copy[t] = _[t].slice();

    return new $SYAW$var$Dispatch(copy);
  },
  call: function (type, that) {
    if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2];
    if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);

    for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
  },
  apply: function (type, that, args) {
    if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);

    for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
  }
};

function $SYAW$var$get(type, name) {
  for (var i = 0, n = type.length, c; i < n; ++i) {
    if ((c = type[i]).name === name) {
      return c.value;
    }
  }
}

function $SYAW$var$set(type, name, callback) {
  for (var i = 0, n = type.length; i < n; ++i) {
    if (type[i].name === name) {
      type[i] = $SYAW$var$noop, type = type.slice(0, i).concat(type.slice(i + 1));
      break;
    }
  }

  if (callback != null) type.push({
    name: name,
    value: callback
  });
  return type;
}

var $TZ6S$export$xhtml = "http://www.w3.org/1999/xhtml";
var $TZ6S$export$default = {
  svg: "http://www.w3.org/2000/svg",
  xhtml: $TZ6S$export$xhtml,
  xlink: "http://www.w3.org/1999/xlink",
  xml: "http://www.w3.org/XML/1998/namespace",
  xmlns: "http://www.w3.org/2000/xmlns/"
};

var $Sva1$export$default = function (name) {
  var prefix = name += "",
      i = prefix.indexOf(":");
  if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
  return $TZ6S$export$default.hasOwnProperty(prefix) ? {
    space: $TZ6S$export$default[prefix],
    local: name
  } : name;
};

function $vTHN$var$creatorInherit(name) {
  return function () {
    var document = this.ownerDocument,
        uri = this.namespaceURI;
    return uri === $TZ6S$export$xhtml && document.documentElement.namespaceURI === $TZ6S$export$xhtml ? document.createElement(name) : document.createElementNS(uri, name);
  };
}

function $vTHN$var$creatorFixed(fullname) {
  return function () {
    return this.ownerDocument.createElementNS(fullname.space, fullname.local);
  };
}

var $vTHN$export$default = function (name) {
  var fullname = $Sva1$export$default(name);
  return (fullname.local ? $vTHN$var$creatorFixed : $vTHN$var$creatorInherit)(fullname);
};

function $hX8m$var$none() {}

var $hX8m$export$default = function (selector) {
  return selector == null ? $hX8m$var$none : function () {
    return this.querySelector(selector);
  };
};

var $um0S$export$default = function (select) {
  if (typeof select !== "function") select = $hX8m$export$default(select);

  for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
    for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) {
      if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) {
        if ("__data__" in node) subnode.__data__ = node.__data__;
        subgroup[i] = subnode;
      }
    }
  }

  return new $Pd8D$export$Selection(subgroups, this._parents);
};

function $sRe$var$empty() {
  return [];
}

var $sRe$export$default = function (selector) {
  return selector == null ? $sRe$var$empty : function () {
    return this.querySelectorAll(selector);
  };
};

var $EUV$export$default = function (select) {
  if (typeof select !== "function") select = $sRe$export$default(select);

  for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) {
    for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
      if (node = group[i]) {
        subgroups.push(select.call(node, node.__data__, i, group));
        parents.push(node);
      }
    }
  }

  return new $Pd8D$export$Selection(subgroups, parents);
};

var $LbZ4$export$default = function (selector) {
  return function () {
    return this.matches(selector);
  };
};

var $hZf9$export$default = function (match) {
  if (typeof match !== "function") match = $LbZ4$export$default(match);

  for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
    for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) {
      if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
        subgroup.push(node);
      }
    }
  }

  return new $Pd8D$export$Selection(subgroups, this._parents);
};

var $NxKh$export$default = function (update) {
  return new Array(update.length);
};

var $BlDg$export$default = function () {
  return new $Pd8D$export$Selection(this._enter || this._groups.map($NxKh$export$default), this._parents);
};

function $BlDg$export$EnterNode(parent, datum) {
  this.ownerDocument = parent.ownerDocument;
  this.namespaceURI = parent.namespaceURI;
  this._next = null;
  this._parent = parent;
  this.__data__ = datum;
}

$BlDg$export$EnterNode.prototype = {
  constructor: $BlDg$export$EnterNode,
  appendChild: function (child) {
    return this._parent.insertBefore(child, this._next);
  },
  insertBefore: function (child, next) {
    return this._parent.insertBefore(child, next);
  },
  querySelector: function (selector) {
    return this._parent.querySelector(selector);
  },
  querySelectorAll: function (selector) {
    return this._parent.querySelectorAll(selector);
  }
};

var $QcpV$export$default = function (x) {
  return function () {
    return x;
  };
};

var $U33$var$keyPrefix = "$"; // Protect against keys like “__proto__”.

function $U33$var$bindIndex(parent, group, enter, update, exit, data) {
  var i = 0,
      node,
      groupLength = group.length,
      dataLength = data.length; // Put any non-null nodes that fit into update.
  // Put any null nodes into enter.
  // Put any remaining data into enter.

  for (; i < dataLength; ++i) {
    if (node = group[i]) {
      node.__data__ = data[i];
      update[i] = node;
    } else {
      enter[i] = new $BlDg$export$EnterNode(parent, data[i]);
    }
  } // Put any non-null nodes that don’t fit into exit.


  for (; i < groupLength; ++i) {
    if (node = group[i]) {
      exit[i] = node;
    }
  }
}

function $U33$var$bindKey(parent, group, enter, update, exit, data, key) {
  var i,
      node,
      nodeByKeyValue = {},
      groupLength = group.length,
      dataLength = data.length,
      keyValues = new Array(groupLength),
      keyValue; // Compute the key for each node.
  // If multiple nodes have the same key, the duplicates are added to exit.

  for (i = 0; i < groupLength; ++i) {
    if (node = group[i]) {
      keyValues[i] = keyValue = $U33$var$keyPrefix + key.call(node, node.__data__, i, group);

      if (keyValue in nodeByKeyValue) {
        exit[i] = node;
      } else {
        nodeByKeyValue[keyValue] = node;
      }
    }
  } // Compute the key for each datum.
  // If there a node associated with this key, join and add it to update.
  // If there is not (or the key is a duplicate), add it to enter.


  for (i = 0; i < dataLength; ++i) {
    keyValue = $U33$var$keyPrefix + key.call(parent, data[i], i, data);

    if (node = nodeByKeyValue[keyValue]) {
      update[i] = node;
      node.__data__ = data[i];
      nodeByKeyValue[keyValue] = null;
    } else {
      enter[i] = new $BlDg$export$EnterNode(parent, data[i]);
    }
  } // Add any remaining nodes that were not bound to data to exit.


  for (i = 0; i < groupLength; ++i) {
    if ((node = group[i]) && nodeByKeyValue[keyValues[i]] === node) {
      exit[i] = node;
    }
  }
}

var $U33$export$default = function (value, key) {
  if (!value) {
    data = new Array(this.size()), j = -1;
    this.each(function (d) {
      data[++j] = d;
    });
    return data;
  }

  var bind = key ? $U33$var$bindKey : $U33$var$bindIndex,
      parents = this._parents,
      groups = this._groups;
  if (typeof value !== "function") value = $QcpV$export$default(value);

  for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) {
    var parent = parents[j],
        group = groups[j],
        groupLength = group.length,
        data = value.call(parent, parent && parent.__data__, j, parents),
        dataLength = data.length,
        enterGroup = enter[j] = new Array(dataLength),
        updateGroup = update[j] = new Array(dataLength),
        exitGroup = exit[j] = new Array(groupLength);
    bind(parent, group, enterGroup, updateGroup, exitGroup, data, key); // Now connect the enter nodes to their following update node, such that
    // appendChild can insert the materialized enter node before this node,
    // rather than at the end of the parent node.

    for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
      if (previous = enterGroup[i0]) {
        if (i0 >= i1) i1 = i0 + 1;

        while (!(next = updateGroup[i1]) && ++i1 < dataLength);

        previous._next = next || null;
      }
    }
  }

  update = new $Pd8D$export$Selection(update, parents);
  update._enter = enter;
  update._exit = exit;
  return update;
};

var $uwqx$export$default = function () {
  return new $Pd8D$export$Selection(this._exit || this._groups.map($NxKh$export$default), this._parents);
};

var $tBqC$export$default = function (onenter, onupdate, onexit) {
  var enter = this.enter(),
      update = this,
      exit = this.exit();
  enter = typeof onenter === "function" ? onenter(enter) : enter.append(onenter + "");
  if (onupdate != null) update = onupdate(update);
  if (onexit == null) exit.remove();else onexit(exit);
  return enter && update ? enter.merge(update).order() : update;
};

var $oMRz$export$default = function (selection) {
  for (var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) {
    for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) {
      if (node = group0[i] || group1[i]) {
        merge[i] = node;
      }
    }
  }

  for (; j < m0; ++j) {
    merges[j] = groups0[j];
  }

  return new $Pd8D$export$Selection(merges, this._parents);
};

var $DTXT$export$default = function () {
  for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) {
    for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) {
      if (node = group[i]) {
        if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next);
        next = node;
      }
    }
  }

  return this;
};

var $Eo98$export$default = function (compare) {
  if (!compare) compare = $Eo98$var$ascending;

  function compareNode(a, b) {
    return a && b ? compare(a.__data__, b.__data__) : !a - !b;
  }

  for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) {
    for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) {
      if (node = group[i]) {
        sortgroup[i] = node;
      }
    }

    sortgroup.sort(compareNode);
  }

  return new $Pd8D$export$Selection(sortgroups, this._parents).order();
};

function $Eo98$var$ascending(a, b) {
  return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
}

var $del$export$default = function () {
  var callback = arguments[0];
  arguments[0] = this;
  callback.apply(null, arguments);
  return this;
};

var $RJvN$export$default = function () {
  var nodes = new Array(this.size()),
      i = -1;
  this.each(function () {
    nodes[++i] = this;
  });
  return nodes;
};

var $P0$export$default = function () {
  for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
    for (var group = groups[j], i = 0, n = group.length; i < n; ++i) {
      var node = group[i];
      if (node) return node;
    }
  }

  return null;
};

var $cLfp$export$default = function () {
  var size = 0;
  this.each(function () {
    ++size;
  });
  return size;
};

var $fLE2$export$default = function () {
  return !this.node();
};

var $Ex0s$export$default = function (callback) {
  for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
    for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
      if (node = group[i]) callback.call(node, node.__data__, i, group);
    }
  }

  return this;
};

function $kqGm$var$attrRemove(name) {
  return function () {
    this.removeAttribute(name);
  };
}

function $kqGm$var$attrRemoveNS(fullname) {
  return function () {
    this.removeAttributeNS(fullname.space, fullname.local);
  };
}

function $kqGm$var$attrConstant(name, value) {
  return function () {
    this.setAttribute(name, value);
  };
}

function $kqGm$var$attrConstantNS(fullname, value) {
  return function () {
    this.setAttributeNS(fullname.space, fullname.local, value);
  };
}

function $kqGm$var$attrFunction(name, value) {
  return function () {
    var v = value.apply(this, arguments);
    if (v == null) this.removeAttribute(name);else this.setAttribute(name, v);
  };
}

function $kqGm$var$attrFunctionNS(fullname, value) {
  return function () {
    var v = value.apply(this, arguments);
    if (v == null) this.removeAttributeNS(fullname.space, fullname.local);else this.setAttributeNS(fullname.space, fullname.local, v);
  };
}

var $kqGm$export$default = function (name, value) {
  var fullname = $Sva1$export$default(name);

  if (arguments.length < 2) {
    var node = this.node();
    return fullname.local ? node.getAttributeNS(fullname.space, fullname.local) : node.getAttribute(fullname);
  }

  return this.each((value == null ? fullname.local ? $kqGm$var$attrRemoveNS : $kqGm$var$attrRemove : typeof value === "function" ? fullname.local ? $kqGm$var$attrFunctionNS : $kqGm$var$attrFunction : fullname.local ? $kqGm$var$attrConstantNS : $kqGm$var$attrConstant)(fullname, value));
};

var $a7iO$export$default = function (node) {
  return node.ownerDocument && node.ownerDocument.defaultView || // node is a Node
  node.document && node // node is a Window
  || node.defaultView; // node is a Document
};

function $PNU$var$styleRemove(name) {
  return function () {
    this.style.removeProperty(name);
  };
}

function $PNU$var$styleConstant(name, value, priority) {
  return function () {
    this.style.setProperty(name, value, priority);
  };
}

function $PNU$var$styleFunction(name, value, priority) {
  return function () {
    var v = value.apply(this, arguments);
    if (v == null) this.style.removeProperty(name);else this.style.setProperty(name, v, priority);
  };
}

var $PNU$export$default = function (name, value, priority) {
  return arguments.length > 1 ? this.each((value == null ? $PNU$var$styleRemove : typeof value === "function" ? $PNU$var$styleFunction : $PNU$var$styleConstant)(name, value, priority == null ? "" : priority)) : $PNU$export$styleValue(this.node(), name);
};

function $PNU$export$styleValue(node, name) {
  return node.style.getPropertyValue(name) || $a7iO$export$default(node).getComputedStyle(node, null).getPropertyValue(name);
}

function $Qc7K$var$propertyRemove(name) {
  return function () {
    delete this[name];
  };
}

function $Qc7K$var$propertyConstant(name, value) {
  return function () {
    this[name] = value;
  };
}

function $Qc7K$var$propertyFunction(name, value) {
  return function () {
    var v = value.apply(this, arguments);
    if (v == null) delete this[name];else this[name] = v;
  };
}

var $Qc7K$export$default = function (name, value) {
  return arguments.length > 1 ? this.each((value == null ? $Qc7K$var$propertyRemove : typeof value === "function" ? $Qc7K$var$propertyFunction : $Qc7K$var$propertyConstant)(name, value)) : this.node()[name];
};

function $u8Lo$var$classArray(string) {
  return string.trim().split(/^|\s+/);
}

function $u8Lo$var$classList(node) {
  return node.classList || new $u8Lo$var$ClassList(node);
}

function $u8Lo$var$ClassList(node) {
  this._node = node;
  this._names = $u8Lo$var$classArray(node.getAttribute("class") || "");
}

$u8Lo$var$ClassList.prototype = {
  add: function (name) {
    var i = this._names.indexOf(name);

    if (i < 0) {
      this._names.push(name);

      this._node.setAttribute("class", this._names.join(" "));
    }
  },
  remove: function (name) {
    var i = this._names.indexOf(name);

    if (i >= 0) {
      this._names.splice(i, 1);

      this._node.setAttribute("class", this._names.join(" "));
    }
  },
  contains: function (name) {
    return this._names.indexOf(name) >= 0;
  }
};

function $u8Lo$var$classedAdd(node, names) {
  var list = $u8Lo$var$classList(node),
      i = -1,
      n = names.length;

  while (++i < n) list.add(names[i]);
}

function $u8Lo$var$classedRemove(node, names) {
  var list = $u8Lo$var$classList(node),
      i = -1,
      n = names.length;

  while (++i < n) list.remove(names[i]);
}

function $u8Lo$var$classedTrue(names) {
  return function () {
    $u8Lo$var$classedAdd(this, names);
  };
}

function $u8Lo$var$classedFalse(names) {
  return function () {
    $u8Lo$var$classedRemove(this, names);
  };
}

function $u8Lo$var$classedFunction(names, value) {
  return function () {
    (value.apply(this, arguments) ? $u8Lo$var$classedAdd : $u8Lo$var$classedRemove)(this, names);
  };
}

var $u8Lo$export$default = function (name, value) {
  var names = $u8Lo$var$classArray(name + "");

  if (arguments.length < 2) {
    var list = $u8Lo$var$classList(this.node()),
        i = -1,
        n = names.length;

    while (++i < n) if (!list.contains(names[i])) return false;

    return true;
  }

  return this.each((typeof value === "function" ? $u8Lo$var$classedFunction : value ? $u8Lo$var$classedTrue : $u8Lo$var$classedFalse)(names, value));
};

function $sv0a$var$textRemove() {
  this.textContent = "";
}

function $sv0a$var$textConstant(value) {
  return function () {
    this.textContent = value;
  };
}

function $sv0a$var$textFunction(value) {
  return function () {
    var v = value.apply(this, arguments);
    this.textContent = v == null ? "" : v;
  };
}

var $sv0a$export$default = function (value) {
  return arguments.length ? this.each(value == null ? $sv0a$var$textRemove : (typeof value === "function" ? $sv0a$var$textFunction : $sv0a$var$textConstant)(value)) : this.node().textContent;
};

function $IOHV$var$htmlRemove() {
  this.innerHTML = "";
}

function $IOHV$var$htmlConstant(value) {
  return function () {
    this.innerHTML = value;
  };
}

function $IOHV$var$htmlFunction(value) {
  return function () {
    var v = value.apply(this, arguments);
    this.innerHTML = v == null ? "" : v;
  };
}

var $IOHV$export$default = function (value) {
  return arguments.length ? this.each(value == null ? $IOHV$var$htmlRemove : (typeof value === "function" ? $IOHV$var$htmlFunction : $IOHV$var$htmlConstant)(value)) : this.node().innerHTML;
};

function $Bs6$var$raise() {
  if (this.nextSibling) this.parentNode.appendChild(this);
}

var $Bs6$export$default = function () {
  return this.each($Bs6$var$raise);
};

function $s7fh$var$lower() {
  if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild);
}

var $s7fh$export$default = function () {
  return this.each($s7fh$var$lower);
};

var $XBOq$export$default = function (name) {
  var create = typeof name === "function" ? name : $vTHN$export$default(name);
  return this.select(function () {
    return this.appendChild(create.apply(this, arguments));
  });
};

function $DmT$var$constantNull() {
  return null;
}

var $DmT$export$default = function (name, before) {
  var create = typeof name === "function" ? name : $vTHN$export$default(name),
      select = before == null ? $DmT$var$constantNull : typeof before === "function" ? before : $hX8m$export$default(before);
  return this.select(function () {
    return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null);
  });
};

function $lzg5$var$remove() {
  var parent = this.parentNode;
  if (parent) parent.removeChild(this);
}

var $lzg5$export$default = function () {
  return this.each($lzg5$var$remove);
};

function $lcuA$var$selection_cloneShallow() {
  return this.parentNode.insertBefore(this.cloneNode(false), this.nextSibling);
}

function $lcuA$var$selection_cloneDeep() {
  return this.parentNode.insertBefore(this.cloneNode(true), this.nextSibling);
}

var $lcuA$export$default = function (deep) {
  return this.select(deep ? $lcuA$var$selection_cloneDeep : $lcuA$var$selection_cloneShallow);
};

var $rHST$export$default = function (value) {
  return arguments.length ? this.property("__data__", value) : this.node().__data__;
};

var $tgqN$var$filterEvents = {};
var $tgqN$export$event = null;

if (typeof document !== "undefined") {
  var $tgqN$var$element = document.documentElement;

  if (!("onmouseenter" in $tgqN$var$element)) {
    $tgqN$var$filterEvents = {
      mouseenter: "mouseover",
      mouseleave: "mouseout"
    };
  }
}

function $tgqN$var$filterContextListener(listener, index, group) {
  listener = $tgqN$var$contextListener(listener, index, group);
  return function (event) {
    var related = event.relatedTarget;

    if (!related || related !== this && !(related.compareDocumentPosition(this) & 8)) {
      listener.call(this, event);
    }
  };
}

function $tgqN$var$contextListener(listener, index, group) {
  return function (event1) {
    var event0 = $tgqN$export$event; // Events can be reentrant (e.g., focus).

    $tgqN$export$event = event1;

    try {
      listener.call(this, this.__data__, index, group);
    } finally {
      $tgqN$export$event = event0;
    }
  };
}

function $tgqN$var$parseTypenames(typenames) {
  return typenames.trim().split(/^|\s+/).map(function (t) {
    var name = "",
        i = t.indexOf(".");
    if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
    return {
      type: t,
      name: name
    };
  });
}

function $tgqN$var$onRemove(typename) {
  return function () {
    var on = this.__on;
    if (!on) return;

    for (var j = 0, i = -1, m = on.length, o; j < m; ++j) {
      if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) {
        this.removeEventListener(o.type, o.listener, o.capture);
      } else {
        on[++i] = o;
      }
    }

    if (++i) on.length = i;else delete this.__on;
  };
}

function $tgqN$var$onAdd(typename, value, capture) {
  var wrap = $tgqN$var$filterEvents.hasOwnProperty(typename.type) ? $tgqN$var$filterContextListener : $tgqN$var$contextListener;
  return function (d, i, group) {
    var on = this.__on,
        o,
        listener = wrap(value, i, group);
    if (on) for (var j = 0, m = on.length; j < m; ++j) {
      if ((o = on[j]).type === typename.type && o.name === typename.name) {
        this.removeEventListener(o.type, o.listener, o.capture);
        this.addEventListener(o.type, o.listener = listener, o.capture = capture);
        o.value = value;
        return;
      }
    }
    this.addEventListener(typename.type, listener, capture);
    o = {
      type: typename.type,
      name: typename.name,
      value: value,
      listener: listener,
      capture: capture
    };
    if (!on) this.__on = [o];else on.push(o);
  };
}

var $tgqN$export$default = function (typename, value, capture) {
  var typenames = $tgqN$var$parseTypenames(typename + ""),
      i,
      n = typenames.length,
      t;

  if (arguments.length < 2) {
    var on = this.node().__on;

    if (on) for (var j = 0, m = on.length, o; j < m; ++j) {
      for (i = 0, o = on[j]; i < n; ++i) {
        if ((t = typenames[i]).type === o.type && t.name === o.name) {
          return o.value;
        }
      }
    }
    return;
  }

  on = value ? $tgqN$var$onAdd : $tgqN$var$onRemove;
  if (capture == null) capture = false;

  for (i = 0; i < n; ++i) this.each(on(typenames[i], value, capture));

  return this;
};

function $tgqN$export$customEvent(event1, listener, that, args) {
  var event0 = $tgqN$export$event;
  event1.sourceEvent = $tgqN$export$event;
  $tgqN$export$event = event1;

  try {
    return listener.apply(that, args);
  } finally {
    $tgqN$export$event = event0;
  }
}

function $Sfd$var$dispatchEvent(node, type, params) {
  var window = $a7iO$export$default(node),
      event = window.CustomEvent;

  if (typeof event === "function") {
    event = new event(type, params);
  } else {
    event = window.document.createEvent("Event");
    if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail;else event.initEvent(type, false, false);
  }

  node.dispatchEvent(event);
}

function $Sfd$var$dispatchConstant(type, params) {
  return function () {
    return $Sfd$var$dispatchEvent(this, type, params);
  };
}

function $Sfd$var$dispatchFunction(type, params) {
  return function () {
    return $Sfd$var$dispatchEvent(this, type, params.apply(this, arguments));
  };
}

var $Sfd$export$default = function (type, params) {
  return this.each((typeof params === "function" ? $Sfd$var$dispatchFunction : $Sfd$var$dispatchConstant)(type, params));
};

var $Pd8D$export$root = [null];

function $Pd8D$export$Selection(groups, parents) {
  this._groups = groups;
  this._parents = parents;
}

function $Pd8D$export$default() {
  return new $Pd8D$export$Selection([[document.documentElement]], $Pd8D$export$root);
}

$Pd8D$export$Selection.prototype = $Pd8D$export$default.prototype = {
  constructor: $Pd8D$export$Selection,
  select: $um0S$export$default,
  selectAll: $EUV$export$default,
  filter: $hZf9$export$default,
  data: $U33$export$default,
  enter: $BlDg$export$default,
  exit: $uwqx$export$default,
  join: $tBqC$export$default,
  merge: $oMRz$export$default,
  order: $DTXT$export$default,
  sort: $Eo98$export$default,
  call: $del$export$default,
  nodes: $RJvN$export$default,
  node: $P0$export$default,
  size: $cLfp$export$default,
  empty: $fLE2$export$default,
  each: $Ex0s$export$default,
  attr: $kqGm$export$default,
  style: $PNU$export$default,
  property: $Qc7K$export$default,
  classed: $u8Lo$export$default,
  text: $sv0a$export$default,
  html: $IOHV$export$default,
  raise: $Bs6$export$default,
  lower: $s7fh$export$default,
  append: $XBOq$export$default,
  insert: $DmT$export$default,
  remove: $lzg5$export$default,
  clone: $lcuA$export$default,
  datum: $rHST$export$default,
  on: $tgqN$export$default,
  dispatch: $Sfd$export$default
};

var $SawF$export$default = function (selector) {
  return typeof selector === "string" ? new $Pd8D$export$Selection([[document.querySelector(selector)]], [document.documentElement]) : new $Pd8D$export$Selection([[selector]], $Pd8D$export$root);
};

var $F0n$var$nextId = 0;

function $F0n$export$default() {
  return new $F0n$var$Local();
}

function $F0n$var$Local() {
  this._ = "@" + (++$F0n$var$nextId).toString(36);
}

$F0n$var$Local.prototype = $F0n$export$default.prototype = {
  constructor: $F0n$var$Local,
  get: function (node) {
    var id = this._;

    while (!(id in node)) if (!(node = node.parentNode)) return;

    return node[id];
  },
  set: function (node, value) {
    return node[this._] = value;
  },
  remove: function (node) {
    return this._ in node && delete node[this._];
  },
  toString: function () {
    return this._;
  }
};

var $fcL$export$default = function () {
  var current = $tgqN$export$event,
      source;

  while (source = current.sourceEvent) current = source;

  return current;
};

var $tjFE$export$default = function (node, event) {
  var svg = node.ownerSVGElement || node;

  if (svg.createSVGPoint) {
    var point = svg.createSVGPoint();
    point.x = event.clientX, point.y = event.clientY;
    point = point.matrixTransform(node.getScreenCTM().inverse());
    return [point.x, point.y];
  }

  var rect = node.getBoundingClientRect();
  return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop];
};

var $dfC2$export$default = function (node) {
  var event = $fcL$export$default();
  if (event.changedTouches) event = event.changedTouches[0];
  return $tjFE$export$default(node, event);
};

var $XEgr$export$default = function (node, touches, identifier) {
  if (arguments.length < 3) identifier = touches, touches = $fcL$export$default().changedTouches;

  for (var i = 0, n = touches ? touches.length : 0, touch; i < n; ++i) {
    if ((touch = touches[i]).identifier === identifier) {
      return $tjFE$export$default(node, touch);
    }
  }

  return null;
};

function $CQsD$export$nopropagation() {
  $tgqN$export$event.stopImmediatePropagation();
}

var $CQsD$export$default = function () {
  $tgqN$export$event.preventDefault();
  $tgqN$export$event.stopImmediatePropagation();
};

var $JG2T$export$default = function (view) {
  var root = view.document.documentElement,
      selection = $SawF$export$default(view).on("dragstart.drag", $CQsD$export$default, true);

  if ("onselectstart" in root) {
    selection.on("selectstart.drag", $CQsD$export$default, true);
  } else {
    root.__noselect = root.style.MozUserSelect;
    root.style.MozUserSelect = "none";
  }
};

function $JG2T$export$yesdrag(view, noclick) {
  var root = view.document.documentElement,
      selection = $SawF$export$default(view).on("dragstart.drag", null);

  if (noclick) {
    selection.on("click.drag", $CQsD$export$default, true);
    setTimeout(function () {
      selection.on("click.drag", null);
    }, 0);
  }

  if ("onselectstart" in root) {
    selection.on("selectstart.drag", null);
  } else {
    root.style.MozUserSelect = root.__noselect;
    delete root.__noselect;
  }
}

var $l0wH$export$default = function (x) {
  return function () {
    return x;
  };
};

function $Qus$export$default(target, type, subject, id, active, x, y, dx, dy, dispatch) {
  this.target = target;
  this.type = type;
  this.subject = subject;
  this.identifier = id;
  this.active = active;
  this.x = x;
  this.y = y;
  this.dx = dx;
  this.dy = dy;
  this._ = dispatch;
}

$Qus$export$default.prototype.on = function () {
  var value = this._.on.apply(this._, arguments);

  return value === this._ ? this : value;
};

// Ignore right-click, since that should open the context menu.
function $U7NX$var$defaultFilter() {
  return !$tgqN$export$event.button;
}

function $U7NX$var$defaultContainer() {
  return this.parentNode;
}

function $U7NX$var$defaultSubject(d) {
  return d == null ? {
    x: $tgqN$export$event.x,
    y: $tgqN$export$event.y
  } : d;
}

function $U7NX$var$defaultTouchable() {
  return "ontouchstart" in this;
}

var $U7NX$export$default = function () {
  var filter = $U7NX$var$defaultFilter,
      container = $U7NX$var$defaultContainer,
      subject = $U7NX$var$defaultSubject,
      touchable = $U7NX$var$defaultTouchable,
      gestures = {},
      listeners = $SYAW$export$default("start", "drag", "end"),
      active = 0,
      mousedownx,
      mousedowny,
      mousemoving,
      touchending,
      clickDistance2 = 0;

  function drag(selection) {
    selection.on("mousedown.drag", mousedowned).filter(touchable).on("touchstart.drag", touchstarted).on("touchmove.drag", touchmoved).on("touchend.drag touchcancel.drag", touchended).style("touch-action", "none").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)");
  }

  function mousedowned() {
    if (touchending || !filter.apply(this, arguments)) return;
    var gesture = beforestart("mouse", container.apply(this, arguments), $dfC2$export$default, this, arguments);
    if (!gesture) return;
    $SawF$export$default($tgqN$export$event.view).on("mousemove.drag", mousemoved, true).on("mouseup.drag", mouseupped, true);
    $JG2T$export$default($tgqN$export$event.view);
    $CQsD$export$nopropagation();
    mousemoving = false;
    mousedownx = $tgqN$export$event.clientX;
    mousedowny = $tgqN$export$event.clientY;
    gesture("start");
  }

  function mousemoved() {
    $CQsD$export$default();

    if (!mousemoving) {
      var dx = $tgqN$export$event.clientX - mousedownx,
          dy = $tgqN$export$event.clientY - mousedowny;
      mousemoving = dx * dx + dy * dy > clickDistance2;
    }

    gestures.mouse("drag");
  }

  function mouseupped() {
    $SawF$export$default($tgqN$export$event.view).on("mousemove.drag mouseup.drag", null);
    $JG2T$export$yesdrag($tgqN$export$event.view, mousemoving);
    $CQsD$export$default();
    gestures.mouse("end");
  }

  function touchstarted() {
    if (!filter.apply(this, arguments)) return;
    var touches = $tgqN$export$event.changedTouches,
        c = container.apply(this, arguments),
        n = touches.length,
        i,
        gesture;

    for (i = 0; i < n; ++i) {
      if (gesture = beforestart(touches[i].identifier, c, $XEgr$export$default, this, arguments)) {
        $CQsD$export$nopropagation();
        gesture("start");
      }
    }
  }

  function touchmoved() {
    var touches = $tgqN$export$event.changedTouches,
        n = touches.length,
        i,
        gesture;

    for (i = 0; i < n; ++i) {
      if (gesture = gestures[touches[i].identifier]) {
        $CQsD$export$default();
        gesture("drag");
      }
    }
  }

  function touchended() {
    var touches = $tgqN$export$event.changedTouches,
        n = touches.length,
        i,
        gesture;
    if (touchending) clearTimeout(touchending);
    touchending = setTimeout(function () {
      touchending = null;
    }, 500); // Ghost clicks are delayed!

    for (i = 0; i < n; ++i) {
      if (gesture = gestures[touches[i].identifier]) {
        $CQsD$export$nopropagation();
        gesture("end");
      }
    }
  }

  function beforestart(id, container, point, that, args) {
    var p = point(container, id),
        s,
        dx,
        dy,
        sublisteners = listeners.copy();
    if (!$tgqN$export$customEvent(new $Qus$export$default(drag, "beforestart", s, id, active, p[0], p[1], 0, 0, sublisteners), function () {
      if (($tgqN$export$event.subject = s = subject.apply(that, args)) == null) return false;
      dx = s.x - p[0] || 0;
      dy = s.y - p[1] || 0;
      return true;
    })) return;
    return function gesture(type) {
      var p0 = p,
          n;

      switch (type) {
        case "start":
          gestures[id] = gesture, n = active++;
          break;

        case "end":
          delete gestures[id], --active;
        // nobreak

        case "drag":
          p = point(container, id), n = active;
          break;
      }

      $tgqN$export$customEvent(new $Qus$export$default(drag, type, s, id, n, p[0] + dx, p[1] + dy, p[0] - p0[0], p[1] - p0[1], sublisteners), sublisteners.apply, sublisteners, [type, that, args]);
    };
  }

  drag.filter = function (_) {
    return arguments.length ? (filter = typeof _ === "function" ? _ : $l0wH$export$default(!!_), drag) : filter;
  };

  drag.container = function (_) {
    return arguments.length ? (container = typeof _ === "function" ? _ : $l0wH$export$default(_), drag) : container;
  };

  drag.subject = function (_) {
    return arguments.length ? (subject = typeof _ === "function" ? _ : $l0wH$export$default(_), drag) : subject;
  };

  drag.touchable = function (_) {
    return arguments.length ? (touchable = typeof _ === "function" ? _ : $l0wH$export$default(!!_), drag) : touchable;
  };

  drag.on = function () {
    var value = listeners.on.apply(listeners, arguments);
    return value === listeners ? drag : value;
  };

  drag.clickDistance = function (_) {
    return arguments.length ? (clickDistance2 = (_ = +_) * _, drag) : Math.sqrt(clickDistance2);
  };

  return drag;
};

let $ScEs$var$element, $ScEs$var$scale, $ScEs$var$scaleX, $ScEs$var$radar;

function $ScEs$var$getElement(chartInstance, callback) {
  return () => {
    if ($tgqN$export$event) {
      const e = $tgqN$export$event.sourceEvent;
      $ScEs$var$element = chartInstance.getElementAtEvent(e)[0];
      $ScEs$var$radar = chartInstance.config.type == 'radar';
      let scaleName = $ScEs$var$radar ? '_scale' : '_yScale';

      if ($ScEs$var$element) {
        if (chartInstance.data.datasets[$ScEs$var$element['_datasetIndex']].dragData === false || $ScEs$var$element[scaleName].options.dragData === false) {
          $ScEs$var$element = null;
          return;
        }

        $ScEs$var$scale = $ScEs$var$element[scaleName].id;

        if ($ScEs$var$element['_xScale']) {
          $ScEs$var$scaleX = $ScEs$var$element['_xScale'].id;
        }

        if (typeof callback === 'function' && $ScEs$var$element) {
          if (callback(e, chartInstance, $ScEs$var$element) === false) {
            $ScEs$var$element = null;
          }
        }
      }
    }
  };
}

function $ScEs$export$createRounder(multiple, precision) {
  return value => {
    const roundedValue = Math.round(value / multiple) * multiple;
    const roundedValueFixedPrecision = +roundedValue.toFixed(precision);
    return roundedValueFixedPrecision;
  };
}

function $ScEs$export$roundValue(value, multiple, precision) {
  return $ScEs$export$createRounder(multiple, precision)(value);
}

function $ScEs$var$updateData(chartInstance, callback) {
  return () => {
    if ($ScEs$var$element && $tgqN$export$event) {
      const e = $tgqN$export$event.sourceEvent;
      const datasetIndex = $ScEs$var$element['_datasetIndex'];
      const index = $ScEs$var$element['_index'];
      const roundMultipleX = chartInstance.options.dragDataRoundMultipleX;
      const roundPrecisionX = chartInstance.options.dragDataRoundPrecisionX;
      const roundMultipleY = chartInstance.options.dragDataRoundMultipleY;
      const roundPrecisionY = chartInstance.options.dragDataRoundPrecisionY;
      const roundX = $ScEs$export$createRounder(roundMultipleX, roundPrecisionX);
      const roundY = $ScEs$export$createRounder(roundMultipleY, roundPrecisionY);
      let x;
      let y;
      const dataRef = chartInstance.data.datasets[datasetIndex].data;
      let datumRef = dataRef[index];
      let proposedDatum = {
        x: datumRef.x,
        y: datumRef.y
      };

      if ($ScEs$var$radar) {
        let v;

        if (e.touches) {
          x = e.touches[0].clientX - chartInstance.canvas.getBoundingClientRect().left;
          y = e.touches[0].clientY - chartInstance.canvas.getBoundingClientRect().top;
        } else {
          x = e.clientX - chartInstance.canvas.getBoundingClientRect().left;
          y = e.clientY - chartInstance.canvas.getBoundingClientRect().top;
        }

        let rScale = chartInstance.scales[$ScEs$var$scale];
        let d = Math.sqrt(Math.pow(x - rScale.xCenter, 2) + Math.pow(y - rScale.yCenter, 2));
        let scalingFactor = rScale.drawingArea / (rScale.max - rScale.min);

        if (rScale.options.ticks.reverse) {
          v = rScale.max - d / scalingFactor;
        } else {
          v = rScale.min + d / scalingFactor;
        }

        v = $ScEs$export$roundValue(chartInstance.options.dragDataRound, 2)(v);
        v = Math.min(v, chartInstance.scale.max);
        v = Math.max(v, chartInstance.scale.min);
        proposedDatum = v;
      } else {
        if (e.touches) {
          x = chartInstance.scales[$ScEs$var$scaleX].getValueForPixel(e.touches[0].clientX - chartInstance.canvas.getBoundingClientRect().left);
          y = chartInstance.scales[$ScEs$var$scale].getValueForPixel(e.touches[0].clientY - chartInstance.canvas.getBoundingClientRect().top);
        } else {
          x = chartInstance.scales[$ScEs$var$scaleX].getValueForPixel(e.clientX - chartInstance.canvas.getBoundingClientRect().left);
          y = chartInstance.scales[$ScEs$var$scale].getValueForPixel(e.clientY - chartInstance.canvas.getBoundingClientRect().top);
        }

        x = roundX(x);
        y = roundY(y);
        x = Math.min(x, chartInstance.scales[$ScEs$var$scaleX].max);
        x = Math.max(x, chartInstance.scales[$ScEs$var$scaleX].min);
        y = Math.min(y, chartInstance.scales[$ScEs$var$scale].max);
        y = Math.max(y, chartInstance.scales[$ScEs$var$scale].min);
        proposedDatum.x = x;

        if (datumRef.y !== undefined) {
          proposedDatum.y = y;
        } else {
          proposedDatum = y;
        }
      }

      let shouldChartUpdateX = chartInstance.options.dragX && datumRef.x !== undefined;
      let shouldChartUpdateY = chartInstance.options.dragY;
      let shouldChartUpdate;

      if (typeof callback === 'function') {
        shouldChartUpdate = callback(e, chartInstance, datasetIndex, index, datumRef, proposedDatum);
        shouldChartUpdateX = shouldChartUpdateX && shouldChartUpdate.dragX;
        shouldChartUpdateY = shouldChartUpdateY && shouldChartUpdate.dragY;
      }

      if (shouldChartUpdateX !== false) {
        datumRef.x = proposedDatum.x;
      }

      if (shouldChartUpdateY !== false) {
        if (datumRef.y !== undefined) {
          datumRef.y = proposedDatum.y;
        } else {
          datumRef = proposedDatum;
        }
      }

      if (shouldChartUpdateX !== false || shouldChartUpdateY !== false) {
        chartInstance.update(0);
      }
    }
  };
}

function $ScEs$var$dragEndCallback(chartInstance, callback) {
  return () => {
    if (typeof callback === 'function' && $ScEs$var$element) {
      const e = $tgqN$export$event.sourceEvent;
      const datasetIndex = $ScEs$var$element['_datasetIndex'];
      const index = $ScEs$var$element['_index'];
      const value = chartInstance.data.datasets[datasetIndex].data[index];
      return callback(e, chartInstance, datasetIndex, index, value);
    }
  };
}

const $ScEs$export$default = {
  afterInit: function afterInit(chartInstance) {
    if (chartInstance.options.dragData) {
      $SawF$export$default(chartInstance.chart.canvas).call($U7NX$export$default().container(chartInstance.chart.canvas).on('start', $ScEs$var$getElement(chartInstance, chartInstance.options.onDragStart)).on('drag', $ScEs$var$updateData(chartInstance, chartInstance.options.onDrag)).on('end', $ScEs$var$dragEndCallback(chartInstance, chartInstance.options.onDragEnd)));
    }
  }
};
var $JsLj$$interop$default = $parcel$interopDefault($JsLj$exports);
$JsLj$$interop$default.d.pluginService.register($ScEs$export$default);

function $ATE0$export$cubicInOut(t) {
  return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2;
}

var $xJOL$var$exponent = 3;

var $xJOL$export$polyIn = function custom(e) {
  e = +e;

  function polyIn(t) {
    return Math.pow(t, e);
  }

  polyIn.exponent = custom;
  return polyIn;
}($xJOL$var$exponent);

var $xJOL$export$polyOut = function custom(e) {
  e = +e;

  function polyOut(t) {
    return 1 - Math.pow(1 - t, e);
  }

  polyOut.exponent = custom;
  return polyOut;
}($xJOL$var$exponent);

var $xJOL$export$polyInOut = function custom(e) {
  e = +e;

  function polyInOut(t) {
    return ((t *= 2) <= 1 ? Math.pow(t, e) : 2 - Math.pow(2 - t, e)) / 2;
  }

  polyInOut.exponent = custom;
  return polyInOut;
}($xJOL$var$exponent);

var $aKKp$var$pi = Math.PI;
var $N$var$overshoot = 1.70158;

var $N$export$backIn = function custom(s) {
  s = +s;

  function backIn(t) {
    return t * t * ((s + 1) * t - s);
  }

  backIn.overshoot = custom;
  return backIn;
}($N$var$overshoot);

var $N$export$backOut = function custom(s) {
  s = +s;

  function backOut(t) {
    return --t * t * ((s + 1) * t + s) + 1;
  }

  backOut.overshoot = custom;
  return backOut;
}($N$var$overshoot);

var $N$export$backInOut = function custom(s) {
  s = +s;

  function backInOut(t) {
    return ((t *= 2) < 1 ? t * t * ((s + 1) * t - s) : (t -= 2) * t * ((s + 1) * t + s) + 2) / 2;
  }

  backInOut.overshoot = custom;
  return backInOut;
}($N$var$overshoot);

var $ZzFe$var$tau = 2 * Math.PI,
    $ZzFe$var$amplitude = 1,
    $ZzFe$var$period = 0.3;

var $ZzFe$export$elasticIn = function custom(a, p) {
  var s = Math.asin(1 / (a = Math.max(1, a))) * (p /= $ZzFe$var$tau);

  function elasticIn(t) {
    return a * Math.pow(2, 10 * --t) * Math.sin((s - t) / p);
  }

  elasticIn.amplitude = function (a) {
    return custom(a, p * $ZzFe$var$tau);
  };

  elasticIn.period = function (p) {
    return custom(a, p);
  };

  return elasticIn;
}($ZzFe$var$amplitude, $ZzFe$var$period);

var $ZzFe$export$elasticOut = function custom(a, p) {
  var s = Math.asin(1 / (a = Math.max(1, a))) * (p /= $ZzFe$var$tau);

  function elasticOut(t) {
    return 1 - a * Math.pow(2, -10 * (t = +t)) * Math.sin((t + s) / p);
  }

  elasticOut.amplitude = function (a) {
    return custom(a, p * $ZzFe$var$tau);
  };

  elasticOut.period = function (p) {
    return custom(a, p);
  };

  return elasticOut;
}($ZzFe$var$amplitude, $ZzFe$var$period);

var $ZzFe$export$elasticInOut = function custom(a, p) {
  var s = Math.asin(1 / (a = Math.max(1, a))) * (p /= $ZzFe$var$tau);

  function elasticInOut(t) {
    return ((t = t * 2 - 1) < 0 ? a * Math.pow(2, 10 * t) * Math.sin((s - t) / p) : 2 - a * Math.pow(2, -10 * t) * Math.sin((s + t) / p)) / 2;
  }

  elasticInOut.amplitude = function (a) {
    return custom(a, p * $ZzFe$var$tau);
  };

  elasticInOut.period = function (p) {
    return custom(a, p);
  };

  return elasticInOut;
}($ZzFe$var$amplitude, $ZzFe$var$period);

// ASSET: tooltips.ts
var $zc7V$var$_temp;

var $zHQ$var$_createClass = function () {
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  return function (Constructor, protoProps, staticProps) {
    if (protoProps) defineProperties(Constructor.prototype, protoProps);
    if (staticProps) defineProperties(Constructor, staticProps);
    return Constructor;
  };
}();

var $zHQ$var$_templateObject = $zHQ$var$_taggedTemplateLiteral(['', ''], ['', '']);

function $zHQ$var$_taggedTemplateLiteral(strings, raw) {
  return Object.freeze(Object.defineProperties(strings, {
    raw: {
      value: Object.freeze(raw)
    }
  }));
}

function $zHQ$var$_classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}
/**
 * @class TemplateTag
 * @classdesc Consumes a pipeline of composable transformer plugins and produces a template tag.
 */


var $zHQ$export$default = function () {
  /**
   * constructs a template tag
   * @constructs TemplateTag
   * @param  {...Object} [...transformers] - an array or arguments list of transformers
   * @return {Function}                    - a template tag
   */
  function TemplateTag() {
    var _this = this;

    for (var _len = arguments.length, transformers = Array(_len), _key = 0; _key < _len; _key++) {
      transformers[_key] = arguments[_key];
    }

    $zHQ$var$_classCallCheck(this, TemplateTag);

    this.tag = function (strings) {
      for (var _len2 = arguments.length, expressions = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
        expressions[_key2 - 1] = arguments[_key2];
      }

      if (typeof strings === 'function') {
        // if the first argument passed is a function, assume it is a template tag and return
        // an intermediary tag that processes the template using the aforementioned tag, passing the
        // result to our tag
        return _this.interimTag.bind(_this, strings);
      }

      if (typeof strings === 'string') {
        // if the first argument passed is a string, just transform it
        return _this.transformEndResult(strings);
      } // else, return a transformed end result of processing the template with our tag


      strings = strings.map(_this.transformString.bind(_this));
      return _this.transformEndResult(strings.reduce(_this.processSubstitutions.bind(_this, expressions)));
    }; // if first argument is an array, extrude it as a list of transformers


    if (transformers.length > 0 && Array.isArray(transformers[0])) {
      transformers = transformers[0];
    } // if any transformers are functions, this means they are not initiated - automatically initiate them


    this.transformers = transformers.map(function (transformer) {
      return typeof transformer === 'function' ? transformer() : transformer;
    }); // return an ES2015 template tag

    return this.tag;
  }
  /**
   * Applies all transformers to a template literal tagged with this method.
   * If a function is passed as the first argument, assumes the function is a template tag
   * and applies it to the template, returning a template tag.
   * @param  {(Function|String|Array<String>)} strings        - Either a template tag or an array containing template strings separated by identifier
   * @param  {...*}                            ...expressions - Optional list of substitution values.
   * @return {(String|Function)}                              - Either an intermediary tag function or the results of processing the template.
   */


  $zHQ$var$_createClass(TemplateTag, [{
    key: 'interimTag',

    /**
     * An intermediary template tag that receives a template tag and passes the result of calling the template with the received
     * template tag to our own template tag.
     * @param  {Function}        nextTag          - the received template tag
     * @param  {Array<String>}   template         - the template to process
     * @param  {...*}            ...substitutions - `substitutions` is an array of all substitutions in the template
     * @return {*}                                - the final processed value
     */
    value: function interimTag(previousTag, template) {
      for (var _len3 = arguments.length, substitutions = Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) {
        substitutions[_key3 - 2] = arguments[_key3];
      }

      return this.tag($zHQ$var$_templateObject, previousTag.apply(undefined, [template].concat(substitutions)));
    }
    /**
     * Performs bulk processing on the tagged template, transforming each substitution and then
     * concatenating the resulting values into a string.
     * @param  {Array<*>} substitutions - an array of all remaining substitutions present in this template
     * @param  {String}   resultSoFar   - this iteration's result string so far
     * @param  {String}   remainingPart - the template chunk after the current substitution
     * @return {String}                 - the result of joining this iteration's processed substitution with the result
     */

  }, {
    key: 'processSubstitutions',
    value: function processSubstitutions(substitutions, resultSoFar, remainingPart) {
      var substitution = this.transformSubstitution(substitutions.shift(), resultSoFar);
      return ''.concat(resultSoFar, substitution, remainingPart);
    }
    /**
     * Iterate through each transformer, applying the transformer's `onString` method to the template
     * strings before all substitutions are processed.
     * @param {String}  str - The input string
     * @return {String}     - The final results of processing each transformer
     */

  }, {
    key: 'transformString',
    value: function transformString(str) {
      var cb = function cb(res, transform) {
        return transform.onString ? transform.onString(res) : res;
      };

      return this.transformers.reduce(cb, str);
    }
    /**
     * When a substitution is encountered, iterates through each transformer and applies the transformer's
     * `onSubstitution` method to the substitution.
     * @param  {*}      substitution - The current substitution
     * @param  {String} resultSoFar  - The result up to and excluding this substitution.
     * @return {*}                   - The final result of applying all substitution transformations.
     */

  }, {
    key: 'transformSubstitution',
    value: function transformSubstitution(substitution, resultSoFar) {
      var cb = function cb(res, transform) {
        return transform.onSubstitution ? transform.onSubstitution(res, resultSoFar) : res;
      };

      return this.transformers.reduce(cb, substitution);
    }
    /**
     * Iterates through each transformer, applying the transformer's `onEndResult` method to the
     * template literal after all substitutions have finished processing.
     * @param  {String} endResult - The processed template, just before it is returned from the tag
     * @return {String}           - The final results of processing each transformer
     */

  }, {
    key: 'transformEndResult',
    value: function transformEndResult(endResult) {
      var cb = function cb(res, transform) {
        return transform.onEndResult ? transform.onEndResult(res) : res;
      };

      return this.transformers.reduce(cb, endResult);
    }
  }]);
  return TemplateTag;
}();

/**
 * TemplateTag transformer that trims whitespace on the end result of a tagged template
 * @param  {String} side = '' - The side of the string to trim. Can be 'start' or 'end' (alternatively 'left' or 'right')
 * @return {Object}           - a TemplateTag transformer
 */
var $POTh$export$default = function trimResultTransformer() {
  var side = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
  return {
    onEndResult: function onEndResult(endResult) {
      if (side === '') {
        return endResult.trim();
      }

      side = side.toLowerCase();

      if (side === 'start' || side === 'left') {
        return endResult.replace(/^\s*/, '');
      }

      if (side === 'end' || side === 'right') {
        return endResult.replace(/\s*$/, '');
      }

      throw new Error('Side not supported: ' + side);
    }
  };
};

function $aIss$var$_toConsumableArray(arr) {
  if (Array.isArray(arr)) {
    for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {
      arr2[i] = arr[i];
    }

    return arr2;
  } else {
    return Array.from(arr);
  }
}
/**
 * strips indentation from a template literal
 * @param  {String} type = 'initial' - whether to remove all indentation or just leading indentation. can be 'all' or 'initial'
 * @return {Object}                  - a TemplateTag transformer
 */


var $aIss$export$default = function stripIndentTransformer() {
  var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'initial';
  return {
    onEndResult: function onEndResult(endResult) {
      if (type === 'initial') {
        // remove the shortest leading indentation from each line
        var match = endResult.match(/^[^\S\n]*(?=\S)/gm);
        var indent = match && Math.min.apply(Math, $aIss$var$_toConsumableArray(match.map(function (el) {
          return el.length;
        })));

        if (indent) {
          var regexp = new RegExp('^.{' + indent + '}', 'gm');
          return endResult.replace(regexp, '');
        }

        return endResult;
      }

      if (type === 'all') {
        // remove all indentation from each line
        return endResult.replace(/^[^\S\n]+/gm, '');
      }

      throw new Error('Unknown type: ' + type);
    }
  };
};

/**
 * Replaces tabs, newlines and spaces with the chosen value when they occur in sequences
 * @param  {(String|RegExp)} replaceWhat - the value or pattern that should be replaced
 * @param  {*}               replaceWith - the replacement value
 * @return {Object}                      - a TemplateTag transformer
 */
var $wXP$export$default = function replaceResultTransformer(replaceWhat, replaceWith) {
  return {
    onEndResult: function onEndResult(endResult) {
      if (replaceWhat == null || replaceWith == null) {
        throw new Error('replaceResultTransformer requires at least 2 arguments.');
      }

      return endResult.replace(replaceWhat, replaceWith);
    }
  };
};

var $yRgv$export$default = function replaceSubstitutionTransformer(replaceWhat, replaceWith) {
  return {
    onSubstitution: function onSubstitution(substitution, resultSoFar) {
      if (replaceWhat == null || replaceWith == null) {
        throw new Error('replaceSubstitutionTransformer requires at least 2 arguments.');
      } // Do not touch if null or undefined


      if (substitution == null) {
        return substitution;
      } else {
        return substitution.toString().replace(replaceWhat, replaceWith);
      }
    }
  };
};

var $T9vP$var$defaults = {
  separator: '',
  conjunction: '',
  serial: false
};
/**
 * Converts an array substitution to a string containing a list
 * @param  {String} [opts.separator = ''] - the character that separates each item
 * @param  {String} [opts.conjunction = '']  - replace the last separator with this
 * @param  {Boolean} [opts.serial = false] - include the separator before the conjunction? (Oxford comma use-case)
 *
 * @return {Object}                     - a TemplateTag transformer
 */

var $T9vP$export$default = function inlineArrayTransformer() {
  var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : $T9vP$var$defaults;
  return {
    onSubstitution: function onSubstitution(substitution, resultSoFar) {
      // only operate on arrays
      if (Array.isArray(substitution)) {
        var arrayLength = substitution.length;
        var separator = opts.separator;
        var conjunction = opts.conjunction;
        var serial = opts.serial; // join each item in the array into a string where each item is separated by separator
        // be sure to maintain indentation

        var indent = resultSoFar.match(/(\n?[^\S\n]+)$/);

        if (indent) {
          substitution = substitution.join(separator + indent[1]);
        } else {
          substitution = substitution.join(separator + ' ');
        } // if conjunction is set, replace the last separator with conjunction, but only if there is more than one substitution


        if (conjunction && arrayLength > 1) {
          var separatorIndex = substitution.lastIndexOf(separator);
          substitution = substitution.slice(0, separatorIndex) + (serial ? separator : '') + ' ' + conjunction + substitution.slice(separatorIndex + 1);
        }
      }

      return substitution;
    }
  };
};

var $OfC$export$default = function splitStringTransformer(splitBy) {
  return {
    onSubstitution: function onSubstitution(substitution, resultSoFar) {
      if (splitBy != null && typeof splitBy === 'string') {
        if (typeof substitution === 'string' && substitution.includes(splitBy)) {
          substitution = substitution.split(splitBy);
        }
      } else {
        throw new Error('You need to specify a string character to split by.');
      }

      return substitution;
    }
  };
};

var $sSuZ$var$isValidValue = function isValidValue(x) {
  return x != null && !Number.isNaN(x) && typeof x !== 'boolean';
};

var $sSuZ$export$default = function removeNonPrintingValuesTransformer() {
  return {
    onSubstitution: function onSubstitution(substitution) {
      if (Array.isArray(substitution)) {
        return substitution.filter($sSuZ$var$isValidValue);
      }

      if ($sSuZ$var$isValidValue(substitution)) {
        return substitution;
      }

      return '';
    }
  };
};

var $ANj$export$default = new $zHQ$export$default($T9vP$export$default({
  separator: ','
}), $aIss$export$default, $POTh$export$default);
var $KTo5$export$default = new $zHQ$export$default($T9vP$export$default({
  separator: ',',
  conjunction: 'and'
}), $aIss$export$default, $POTh$export$default);
var $p4N3$export$default = new $zHQ$export$default($T9vP$export$default({
  separator: ',',
  conjunction: 'or'
}), $aIss$export$default, $POTh$export$default);
var $Xu5K$export$default = new $zHQ$export$default($OfC$export$default('\n'), $sSuZ$export$default, $T9vP$export$default, $aIss$export$default, $POTh$export$default);
var $unBw$export$default = new $zHQ$export$default($OfC$export$default('\n'), $T9vP$export$default, $aIss$export$default, $POTh$export$default, $yRgv$export$default(/&/g, '&amp;'), $yRgv$export$default(/</g, '&lt;'), $yRgv$export$default(/>/g, '&gt;'), $yRgv$export$default(/"/g, '&quot;'), $yRgv$export$default(/'/g, '&#x27;'), $yRgv$export$default(/`/g, '&#x60;'));
var $Po01$export$default = new $zHQ$export$default($wXP$export$default(/(?:\n(?:\s*))+/g, ' '), $POTh$export$default);
var $ShrF$export$default = new $zHQ$export$default($wXP$export$default(/(?:\n\s*)/g, ''), $POTh$export$default);
var $fF1$export$default = new $zHQ$export$default($T9vP$export$default({
  separator: ','
}), $wXP$export$default(/(?:\s+)/g, ' '), $POTh$export$default);
var $xuSy$export$default = new $zHQ$export$default($T9vP$export$default({
  separator: ',',
  conjunction: 'or'
}), $wXP$export$default(/(?:\s+)/g, ' '), $POTh$export$default);
var $cYKW$export$default = new $zHQ$export$default($T9vP$export$default({
  separator: ',',
  conjunction: 'and'
}), $wXP$export$default(/(?:\s+)/g, ' '), $POTh$export$default);
var $Ey4o$export$default = new $zHQ$export$default($T9vP$export$default, $aIss$export$default, $POTh$export$default);
var $CWPs$export$default = new $zHQ$export$default($T9vP$export$default, $wXP$export$default(/(?:\s+)/g, ' '), $POTh$export$default);
var $ciUv$export$default = new $zHQ$export$default($aIss$export$default, $POTh$export$default);
var $l40o$export$default = new $zHQ$export$default($aIss$export$default('all'), $POTh$export$default);
var $zc7V$export$Tooltips;

(function (Tooltips) {
  Tooltips.markerPairNumberTooltip = $ciUv$export$default`
    Enter a new marker pair number here to reorder marker pairs.
    Does not automatically update merge list.
    `;
  Tooltips.speedTooltip = $ciUv$export$default`
    Toggle speed previewing with C.
    When audio is enabled, speeds below 0.5 are not yet supported.
    YouTube can only preview speeds that are multiples of 0.5 (e.g., 0.75 or 0.45).
    YouTube does not support previewing speeds below 0.25.
    `;
  Tooltips.cropTooltip = $ciUv$export$default`
    Crop values are given as x-offset:y-offset:width:height. Each value is a positive integer in pixels.
    Width and height can also be 'iw' and 'ih' respectively for input width and input height.
    Use Ctrl+Click+Drag on the crop preview to adjust the crop with the mouse instead.
    Increment/decrement values in the crop input with the up/down keys by ±10.
    The cursor position within the crop string determines the crop component to change.
    Use modifier keys to alter the change amount: Alt: ±1, Shift: ±50, Alt+Shift: ±100.
    `;
  Tooltips.titlePrefixTooltip = $ciUv$export$default`
    Specify a title prefix to be prepended to the tile suffix of the file name generated by this marker pair.
    `;
  Tooltips.titleSuffixTooltip = $ciUv$export$default`
    Specify a title suffix to be appended to the title prefixes specified for each marker pair.
    The title suffix is followed by the marker pair number in the final file name for each marker pair.
    The title suffix is also the the file name stem of the markers data json file saved with S.
    The markers data file name is used as the title suffix when running the clipper script.
    Thus it can be renamed to change the title suffix without editing the json data.
    `;
  Tooltips.cropResolutionTooltip = $ciUv$export$default`
    The crop resolution specifies the scaling of crop strings, which should match the input video's resolution.
    However, lower crop resolutions can be easier to work with.
    The clipper script will automatically scale the crop resolution if a mismatch is detected.
    `;
  Tooltips.rotateTooltip = $ciUv$export$default`
    Correct video rotation by rotating the input video clockwise or counterclockwise by 90 degrees.
    Note that the YouTube video rotate preview using the R/Alt+R shortcuts does NOT affect the output video. 
    `;
  Tooltips.mergeListTooltip = $ciUv$export$default`
    Specify which marker pairs if any you would like to merge/concatenate.
    Each merge is a comma separated list of marker pair numbers or ranges (e.g., '1-3,5,9' = '1,2,3,5,9').
    Multiple merges are separated with semicolons (e.g., '1-3,5,9;6-2,8' will create two merged webms).
    Merge occurs successfully only after successful generation of each required generated webm.
    Merge does not require reencoding and simply orders each webm into one container.
    `;
  Tooltips.audioTooltip = $ciUv$export$default`
    Enable audio.
    Not yet compatible with special loop behaviors or time-variable speed.
    `;
  Tooltips.encodeSpeedTooltip = $ciUv$export$default`
    Higher values will speed up encoding at the cost of some quality.
    Very high values will also reduce bitrate control effectiveness, which may increase file sizes.
    `;
  Tooltips.CRFTooltip = $ciUv$export$default`
    Constant Rate Factor or CRF allows the video bitrate to vary while maintaining roughly constant quality.
    Lower CRF values result in higher quality but larger file sizes.
    A CRF around 20 (~25 for 4k) usually results in file size compression that does not visibly reduce quality.
    When the target bitrate is set to 0 (unlimited), the bitrate is unconstrained and operates in constant quality mode .
    When the target bitrate is set to auto or a positive value in kbps, the script operates in constrained quality mode.
    Constrained quality mode keeps file sizes reasonable even when low CRF values are specified.
    `;
  Tooltips.targetBitrateTooltip = $ciUv$export$default`
    Specify the target bitrate in kbps of the output video.
    The bitrate determines how much data is used to encode each second of video and thus the final file size.
    If the bitrate is too low then the compression of the video will visibly reduce quality.
    When the target bitrate is set to 0 for unlimited, the script operates in constant quality mode.
    When the target bitrate is set to auto or a positive value in kbps, the script operates in constrained quality mode.
    Constrained quality mode keeps file sizes reasonable even when low CRF values are specified.
    `;
  Tooltips.twoPassTooltip = $ciUv$export$default`
    Encode in two passes for improved bitrate control which can reduce filesizes.
    Results in better quality, with diminishing returns for high bitrate video.
    Significantly reduces encode speed.
    `;
  Tooltips.gammaTooltip = $ciUv$export$default`
    A gamma function is used to map input luminance values to output luminance values or vice versa.
    Note that the gamma preview is not accurate. Use the offline previewer for more accurate gamma preview.
    The gamma value is an exponent applied to the input luminance values.
    A gamma value of 1 is neutral and does not modify the video.
    A gamma value greater than 1 can be used to darken the video and enhance highlight detail.
    A gamma value less than 1 can be used to lighten the video and enhance shadow detail.
    Even small changes in gamma can have large effects (smallest possible change is 0.1).
    Use the gamma preview toggle (Alt+C) to set the gamma to taste.
    `;
  Tooltips.expandColorRangeTooltip = $ciUv$export$default`
    This filter tries to enhance the vividness of colors, make shadows darker, and make highlights brighter.
    The result may not be accurate and your mileage will vary depending on the input video.
    `;
  Tooltips.denoiseTooltip = $ciUv$export$default`
    Reduce noise, static, and blockiness at the cost of some encoding speed.
    Improves compression efficiency and thus reduces file sizes.
    Higher strength presets may result in oversmoothing of details.
    `;
  Tooltips.vidstabTooltip = $ciUv$export$default`
    Video stabilization tries to smooth out the motion in the video and reduce shaking.
    Usually requires the cropping and zooming of the video.
    Higher strength presets result in more cropping and zooming.
    Low contrast video or video with flashing lights may give poor results.
    If the video includes a logo or other static element within the cropped region, \
    video stabilization may cause the logo to shake.
    `;
  Tooltips.dynamicZoomTooltip = $ciUv$export$default`
    Allow cropping and zooming of video to vary with the need for stabilization over time.
    `;
  Tooltips.speedMapTooltip = $ciUv$export$default`
    Time-variable speed maps are enabled by default, but can be force enabled/disabled with this setting.
    A speed map may be specified using the speed chart (toggled with D).
    `;
  Tooltips.loopTooltip = $ciUv$export$default`
    Enable one of the special loop behaviors.
    fwrev loops will play the video normally once, then immediately play it in reverse.
    fade loops will crossfade the end of the video into the start of the video.
    fade loops can make short clips easier on the eyes and reduce the perceived jerkiness when the video repeats.
    `;
  Tooltips.fadeDurationTooltip = $ciUv$export$default`
    The duration to cut from the beginning and end of the output video to produce the crossfade for fade loops.
    Will be clamped to a minimum of 0.1 seconds and a maximum of 40% of the output clip duration.
    Only applicable when loop is set to fade.
    `;
})($zc7V$export$Tooltips || ($zc7V$var$_temp = $zc7V$export$Tooltips = {}, $zc7V$var$_temp));

function $XbmT$var$_slicedToArray(arr, i) {
  return $XbmT$var$_arrayWithHoles(arr) || $XbmT$var$_iterableToArrayLimit(arr, i) || $XbmT$var$_nonIterableRest();
}

function $XbmT$var$_nonIterableRest() {
  throw new TypeError("Invalid attempt to destructure non-iterable instance");
}

function $XbmT$var$_iterableToArrayLimit(arr, i) {
  var _arr = [];
  var _n = true;
  var _d = false;
  var _e = undefined;

  try {
    for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
      _arr.push(_s.value);

      if (i && _arr.length === i) break;
    }
  } catch (err) {
    _d = true;
    _e = err;
  } finally {
    try {
      if (!_n && _i["return"] != null) _i["return"]();
    } finally {
      if (_d) throw _e;
    }
  }

  return _arr;
}

function $XbmT$var$_arrayWithHoles(arr) {
  if (Array.isArray(arr)) return arr;
}
// ==UserScript==
// @locale       english
// @name         yt_clipper
// @version      0.0.88
// @description  Add markers to youtube videos and generate clipped webms online or offline.
// @author       elwm
// @namespace    https://github.com/exwm
// @homepage     https://github.com/exwm/yt_clipper
// @supportURL   https://github.com/exwm/yt_clipper/issues
// @downloadURL  https://openuserjs.org/src/scripts/elwm/yt_clipper.user.js
// @updateURL    https://openuserjs.org/meta/elwm/yt_clipper.meta.js
// @icon         https://raw.githubusercontent.com/exwm/yt_clipper/master/assets/image/pepe-clipper.gif
// @require      https://cdn.jsdelivr.net/npm/jszip@3.2.1/dist/jszip.min.js
// @require      https://cdn.jsdelivr.net/npm/chart.js@2.8.0/dist/Chart.min.js
// @require      https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@0.6.0/dist/chartjs-plugin-datalabels.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js
// @require      https://gitcdn.xyz/repo/exwm/chartjs-plugin-zoom/master/dist/chartjs-plugin-zoom.min.js
// @require      https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@0.5.7/chartjs-plugin-annotation.min.js
// @run-at       document-end
// @license      MIT
// @match        *://*.youtube.com/*
// @noframes
// @grant        none
// ==/UserScript==


var $XbmT$var$__rest = $XbmT$exports && $XbmT$exports.__rest || function (s, e) {
  var t = {};

  for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];

  if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
    if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
  }
  return t;
};

const $XbmT$var$ytClipperCSS = ":root {\n  --lighter-grey: rgb(235, 235, 235);\n  --light-grey: rgb(210, 210, 210);\n  --med-grey: rgb(110, 110, 110);\n  --dark-grey: rgb(40, 40, 40);\n  --darker-grey: rgb(10, 10, 10);\n  --dark-red: rgb(50, 0, 0);\n  --marker-pair-editor-accent: rgb(245, 118, 0);\n  --global-editor-accent: rgb(245, 0, 0);\n}\n\n@keyframes valid-input {\n  0% {\n    background-color: tomato;\n  }\n  100% {\n    background-color: lightgreen;\n  }\n}\n\n@keyframes invalid-input {\n  0% {\n    background-color: lightgreen;\n  }\n  100% {\n    background-color: tomato;\n  }\n}\n\n@keyframes flash {\n  0% {\n    opacity: 1;\n  }\n  100% {\n    opacity: 0;\n  }\n}\n\n.msg-div,\n#markers-upload-div,\n#markers-upload-div input:not([type='file']) {\n  display: inline-block;\n  margin: 4px;\n  padding: 4px;\n  color: var(--light-grey);\n  background: var(--dark-grey);\n  box-shadow: 2px 2px 3px 0px var(--darker-grey);\n  border-radius: 2px;\n  border-color: var(--med-grey);\n  border-width: 1px 0px 0px 1px;\n  font-size: 10pt;\n  font-weight: 500;\n}\n\n#markers-upload-div {\n  display: block;\n}\n\n.flash-div {\n  animation-name: flash;\n  animation-duration: 5s;\n  animation-fill-mode: forwards;\n}\n\n.marker {\n  width: 1px;\n  height: 14px;\n}\n\n.start-marker {\n  fill: lime;\n  pointer-events: none;\n}\n\n.end-marker {\n  fill: gold;\n  pointer-events: visibleFill;\n}\n\n.selected-marker-overlay {\n  fill: black;\n  width: 1px;\n  height: 8px;\n  pointer-events: none;\n}\n.selected-marker-overlay-hidden {\n  fill-opacity: 0.5;\n}\n\n#settings-editor-div {\n  display: flex;\n}\n\n.settings-editor-panel {\n  display: inline;\n  flex-grow: 1;\n  color: var(--med-grey);\n  font-size: 12pt;\n  margin: 3px;\n  padding: 2px 6px 0px 4px;\n  border: 2px solid var(--med-grey);\n  border-radius: 5px;\n  background: var(--dark-red);\n}\n\n.settings-editor-panel > legend {\n  display: block;\n  width: fit-content;\n  font-size: 14pt;\n  text-shadow: -1px -1px 0 black, 1px -1px black, -1px 1px 0 black, 1px 1px 0 black,\n    -1px 0px 0px black, 0px -1px 0px black, 1px 0px 0px black, 0px 1px 0px black;\n  padding: 0px 4px 0px 4px;\n  margin-left: 12px;\n}\n\n.settings-editor-input-div {\n  display: inline-block;\n  color: grey;\n  font-size: 12pt;\n  margin: 0px 0px 6px 2px;\n  padding: 4px 10px 4px 4px;\n  background: var(--dark-grey);\n  box-shadow: 2px 2px 3px 0px var(--darker-grey);\n  border-radius: 2px;\n  border-color: var(--med-grey);\n  border-style: solid;\n  border-width: 1px 0px 0px 1px;\n  vertical-align: top;\n}\n\n.settings-editor-input-div span {\n  display: block;\n  color: var(--light-grey);\n  margin-bottom: 2px;\n}\n\n.multi-input-div {\n  display: inline-flex;\n  width: fit-content;\n}\n\n.settings-editor-input-div > div {\n  display: inline-block;\n  margin-right: 6px;\n}\n.settings-editor-input-div > div:last-of-type {\n  margin-right: -4px;\n}\n\n.settings-editor-input-div select,\n.settings-editor-input-div input:not([type='radio']),\n#marker-pair-number-input {\n  display: block;\n  font-weight: bold;\n  background: var(--lighter-grey);\n  width: 100%;\n  border: none;\n  box-shadow: #151515 2px 2px 2px 0px;\n}\n\n.settings-editor-input-div input:not([type='radio']),\n#marker-pair-number-input {\n  border-radius: 5px;\n  padding-right: 4px;\n  padding-left: 2px;\n}\n\n.settings-info-display span,\n#global-settings-rotate label,\n#merge-list-div input,\n#global-settings-rotate input,\n#marker-pair-number-input {\n  display: inline;\n  width: auto;\n}\n\n#marker-pair-number-input {\n  vertical-align: top;\n  border: 1px solid black;\n}\n\n.settings-editor-input-div input:valid,\n#marker-pair-number-input:valid {\n  animation-name: valid-input;\n  animation-duration: 1s;\n  animation-fill-mode: forwards;\n}\n\n.settings-editor-input-div input:invalid,\n#marker-pair-number-input:invalid {\n  animation-name: invalid-input;\n  animation-duration: 1s;\n  animation-fill-mode: forwards;\n}\n\n.marker-pair-settings-editor-highlighted-div {\n  border: 2px solid var(--marker-pair-editor-accent) !important;\n}\n\n.global-settings-editor-highlighted-div {\n  border: 2px solid var(--global-editor-accent) !important;\n}\n\n.marker-pair-settings-editor-highlighted-label {\n  color: var(--marker-pair-editor-accent) !important;\n}\n\n.global-settings-editor-highlighted-label {\n  color: var(--global-editor-accent) !important;\n}\n\n#markers-svg,\n#selected-marker-pair-overlay,\n#start-marker-numberings,\n#end-marker-numberings {\n  font-size: 6.5pt;\n  width: 100%;\n  height: 300%;\n  top: -5px;\n  position: absolute;\n  z-index: 99;\n  paint-order: stroke;\n  stroke: rgba(0, 0, 0, 0.25);\n  stroke-width: 2px;\n}\n\n#start-marker-numberings {\n  top: -19px;\n}\n\n#end-marker-numberings {\n  top: 5px;\n}\n\n.markerNumbering {\n  pointer-events: none;\n}\n\n.startMarkerNumbering {\n  fill: lime;\n}\n\n.endMarkerNumbering {\n  fill: gold;\n}\n\n#crop-div {\n  pointer-events: none;\n  z-index: 10;\n}\n\n#begin-crop-preview-div {\n  pointer-events: none;\n  z-index: 11;\n}\n\n#crop-svg,\n#begin-crop-preview-svg {\n  width: 100%;\n  height: 100%;\n  top: 0px;\n  position: absolute;\n}\n";
const $XbmT$var$shortcutsTable = "<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Marker Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Toggle shortcuts on/off</td>\n      <td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd></td>\n    </tr>\n    <tr>\n      <td>Add marker at current time</td>\n      <td><kbd>A</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle targeted marker pair's editor</td>\n      <td><kbd>Shift</kbd> + <kbd>Mouseover</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle marker pair overrides editor</td>\n      <td><kbd>Shift</kbd> + <kbd>W</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle marker pair selection</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Up</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle auto-hiding unselected marker pairs</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Down</kbd></td>\n    </tr>\n    <tr>\n      <td>Undo/redo last marker</td>\n      <td><kbd>Z</kbd> / <kbd>Shift</kbd> + <kbd>Z</kbd></td>\n    </tr>\n    <tr>\n      <td>Delete selected marker pair</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Z</kbd></td>\n    </tr>\n    <tr>\n      <td>Move start/end marker to current time</td>\n      <td><kbd>Shift</kbd> + <kbd>Q</kbd>/<kbd>A</kbd></td>\n    </tr>\n    <tr>\n      <td>\n        Move start/end marker a frame when on left/right half of window\n      </td>\n      <td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Mousewheel</kbd></td>\n    </tr>\n    <tr>\n      <td>Undo/redo last marker move of selected pair</td>\n      <td>\n        <kbd>Alt</kbd> + <kbd>Z</kbd> / <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Z</kbd>\n      </td>\n    </tr>\n    <tr>\n      <td>Redo last marker move of selected pair</td>\n      <td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Z</kbd></td>\n    </tr>\n    <tr>\n      <td>Jump to nearest next/previous marker</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Left</kbd>/<kbd>Right</kbd></td>\n    </tr>\n    <tr>\n      <td>Select next/previous marker pair</td>\n      <td><kbd>Alt</kbd> + <kbd>Left</kbd>/<kbd>Right</kbd></td>\n    </tr>\n    <tr>\n      <td>Select next/previous marker pair and jump to start marker</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Left</kbd>/<kbd>Right</kbd></td>\n    </tr>\n  </tbody>\n</table>\n\n<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Global Settings Editor Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Toggle global settings editor</td>\n      <td><kbd>W</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle encoding settings editor</td>\n      <td><kbd>Shift</kbd> + <kbd>W</kbd></td>\n    </tr>\n    <tr>\n      <td>Update all markers to default new marker speed/crop</td>\n      <td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Q</kbd>/<kbd>X</kbd></td>\n    </tr>\n  </tbody>\n</table>\n\n<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Cropping Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Begin drawing crop</td>\n      <td><kbd>X</kbd></td>\n    </tr>\n    <tr>\n      <td>Begin drawing vertical crop (sets only left and right)</td>\n      <td><kbd>Shift</kbd> + <kbd>X</kbd></td>\n    </tr>\n    <tr>\n      <td>Draw crop</td>\n      <td><kbd>Click</kbd> + <kbd>Drag</kbd></td>\n    </tr>\n    <tr>\n      <td>Move or resize crop</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Drag</kbd></td>\n    </tr>\n    <tr>\n      <td>Crop-aspect-ratio-locked resize of crop</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Drag</kbd></td>\n    </tr>\n    <tr>\n      <td>Cycle crop dim opacity by +0.25</td>\n      <td><kbd>Ctrl</kbd> + <kbd>X</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle crop adjustment with arrow keys</td>\n      <td><kbd>Alt</kbd> + <kbd>X</kbd></td>\n    </tr>\n    <tr>\n      <td>Adjust crop input string with arrow keys</td>\n      <td><pre>Place cursor on target value</pre></td>\n    </tr>\n    <tr>\n      <td>Change crop change amount from 10 to 1/50/100</td>\n      <td><kbd>Alt</kbd> / <kbd>Shift</kbd> / <kbd>Alt</kbd> + <kbd>Shift</kbd></td>\n    </tr>\n    <tr>\n      <td>Modify crop width/height instead of x/y offset</td>\n      <td><kbd>Ctrl</kbd> + <kbd>ArrowKey</kbd></td>\n    </tr>\n  </tbody>\n</table>\n\n<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Preview Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Toggle previewing speed</td>\n      <td><kbd>C</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle auto marker pair looping</td>\n      <td><kbd>Shift</kbd> + <kbd>C</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle previewing gamma</td>\n      <td><kbd>Alt</kbd> + <kbd>C</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle previewing fade loop</td>\n      <td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>C</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle all previews</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>C</kbd></td>\n    </tr>\n    <tr>\n      <td>Cycle playback speed down by 0.25</td>\n      <td><kbd>Q</kbd></td>\n    </tr>\n    <tr>\n      <td>Seek video frame by frame</td>\n      <td>\n        <kbd>&lt;</kbd> / <kbd>&gt;</kbd> or <kbd>Shift</kbd> + <kbd>Mousewheel</kbd>\n      </td>\n    </tr>\n    <tr>\n      <td>Toggle previewing rotation 90&deg; clockwise/anti-clockwise</td>\n      <td><kbd>R</kbd> / <kbd>Alt</kbd> + <kbd>R</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle big video preview thumbnails</td>\n      <td><kbd>Shift</kbd> + <kbd>R</kbd></td>\n    </tr>\n  </tbody>\n</table>\n\n<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Speed Chart Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Toggle speed chart</td>\n      <td><kbd>D</kbd></td>\n    </tr>\n    <tr>\n      <td>Add speed point</td>\n      <td><kbd>Shift</kbd> + <kbd>Click</kbd></td>\n    </tr>\n    <tr>\n      <td>Delete speed point</td>\n      <td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Click</kbd></td>\n    </tr>\n    <tr>\n      <td>Move speed point or pan chart</td>\n      <td><kbd>Click</kbd> + <kbd>Drag</kbd></td>\n    </tr>\n    <tr>\n      <td>Zoom in and out</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Mousewheel</kbd></td>\n    </tr>\n    <tr>\n      <td>Reset zoom</td>\n      <td><kbd>Ctrl</kbd> + <kbd>Click</kbd></td>\n    </tr>\n    <tr>\n      <td>Seek to time on time-axis</td>\n      <td><kbd>Right-Click</kbd></td>\n    </tr>\n    <tr>\n      <td>Set speed chart loop start/end marker</td>\n      <td>\n        <kbd>Alt</kbd> + <kbd>Right-click</kbd> / <kbd>Ctrl</kbd> + <kbd>Alt</kbd> +\n        <kbd>Right-click</kbd>\n      </td>\n    </tr>\n    <tr>\n      <td>Toggle speed chart looping</td>\n      <td><kbd>Shift</kbd> + <kbd>D</kbd></td>\n    </tr>\n    <tr>\n      <td>Reset speed chart looping markers</td>\n      <td><kbd>Alt</kbd> + <kbd>D</kbd></td>\n    </tr>\n  </tbody>\n</table>\n\n<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Frame Capturer Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Capture frame</td>\n      <td><kbd>E</kbd></td>\n    </tr>\n    <tr>\n      <td>Zip and download captured frames</td>\n      <td><kbd>Alt</kbd> + <kbd>E</kbd></td>\n    </tr>\n  </tbody>\n</table>\n\n<table>\n  <thead>\n    <tr>\n      <th colspan=\"2\">Saving Shortcuts</th>\n    </tr>\n    <tr>\n      <th>Action</th>\n      <th>Shortcut</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>Save markers data as json</td>\n      <td><kbd>S</kbd></td>\n    </tr>\n    <tr>\n      <td>Copy markers data to clipboard</td>\n      <td><kbd>Alt</kbd> + <kbd>S</kbd></td>\n    </tr>\n    <tr>\n      <td>Toggle markers data loading dialog</td>\n      <td><kbd>G</kbd></td>\n    </tr>\n  </tbody>\n</table>\n";
const $XbmT$var$shortcutsTableStyle = "#shortcutsTableContainer table {\n  text-align: left;\n  line-height: 32px;\n  border-collapse: separate;\n  border-spacing: 0;\n  border: 2px solid #ed1c40;\n  width: 630px;\n  margin: 8px auto;\n  border-radius: 0.25rem;\n  font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n  font-size: 13px;\n  font-weight: 500;\n  background-color: #222;\n  color: #ddd;\n}\n\n#shortcutsTableContainer thead tr:first-child {\n  background: #ed1c40;\n  color: #fff;\n  border: none;\n}\n\n#shortcutsTableContainer thead tr:nth-child(2) {\n  text-align: center;\n}\n\n#shortcutsTableContainer tr:nth-child(even) {\n  background-color: #333;\n}\n\n#shortcutsTableContainer th:first-child,\ntd:first-child {\n  padding: 0 5px;\n}\n\n#shortcutsTableContainer th {\n  font-size: 14px;\n  font-weight: 600;\n}\n\n#shortcutsTableContainer thead tr:last-child th {\n  border-bottom: 3px solid #777;\n}\n\n#shortcutsTableContainer tbody tr:hover {\n  background-color: #444;\n}\n\n#shortcutsTableContainer tbody tr:first-child {\n  border-bottom: 1px solid #666;\n  border-right: 2px solid #666;\n}\n\n#shortcutsTableContainer td {\n  border-bottom: 1px solid #666;\n}\n\n#shortcutsTableContainer td:first-child {\n  border-right: 2px solid #666;\n}\n\n#shortcutsTableContainer td:last-child {\n  text-align: right;\n  padding-right: 5px;\n}\n\n#shortcutsTableContainer kbd {\n  border: 1px solid #999;\n  border-radius: 2px;\n  font-weight: bold;\n  padding: 2px 4px;\n  margin: 1px;\n  background-color: #e0e0e0;\n  color: #333;\n}\n";
const $XbmT$var$shortcutsTableToggleButtonHTML = "<button\n  id=\"shortcutsTableToggleButton\"\n  class=\"ytp-button\"\n  title=\"Toggle yt_clipper Shortcuts Table\"\n  style=\"cursor: help\"\n>\n  <svg\n    version=\"1.1\"\n    width=\"100%\"\n    height=\"100%\"\n    viewbox=\"0 0 40 40\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <path\n      d=\"M19.66 21.528l5.425 3.972c0.447 0.335 1.26 0.599 1.818 0.599h3.149l-13.319-9.773c0.073-0.27 0.116-0.579 0.116-0.899 0-1.964-1.592-3.556-3.556-3.556s-3.556 1.592-3.556 3.556c0 1.964 1.592 3.556 3.556 3.556 0.759 0 1.462-0.237 2.039-0.643l-0.011 0.008 2.265 1.656-2.265 1.656c-0.568-0.403-1.276-0.644-2.040-0.644-1.964 0-3.556 1.592-3.556 3.556s1.592 3.556 3.556 3.556c1.964 0 3.556-1.592 3.556-3.556 0-0.316-0.041-0.623-0.119-0.915l0.005 0.025 2.946-2.154zM13.29 16.957c-0.841 0-1.524-0.682-1.524-1.524s0.682-1.524 1.524-1.524v0c0.841 0 1.524 0.682 1.524 1.524s-0.682 1.524-1.524 1.524v0zM13.29 26.1c-0.841 0-1.524-0.682-1.524-1.524s0.682-1.524 1.524-1.524v0c0.841 0 1.524 0.682 1.524 1.524s-0.682 1.524-1.524 1.524v0zM25.075 14.508c0.515-0.348 1.143-0.567 1.82-0.599l0.008-0h3.149l-7.619 5.587-2.083-1.524 4.724-3.464z\"\n      fill=\"#fff\"\n    ></path>\n  </svg>\n</button>\n";
const $XbmT$var$__version__ = '0.0.88';
let $XbmT$export$player;
$XbmT$exports.player = $XbmT$export$player;

(function () {
  async function onLoadVideoPage(callback) {
    const ytdapp = await $BHXf$export$retryUntilTruthyResult(() => document.getElementsByTagName('ytd-app')[0]);

    if (ytdapp.hasAttribute('is-watch-page')) {
      console.log('watch page loaded');
      callback();
      return;
    }

    const observer = new MutationObserver(mutationList => {
      mutationList.forEach(mutation => {
        if (mutation.type === 'attributes' && mutation.attributeName === 'is-watch-page' && ytdapp.hasAttribute('is-watch-page')) {
          console.log('watch page loaded');
          observer.disconnect();
          callback();
        }
      });
    });
    const config = {
      attributeFilter: ['is-watch-page']
    };
    console.log(`Waiting for video page load before calling ${callback.name}`);
    observer.observe(ytdapp, config);
  }

  onLoadVideoPage(loadytClipper);

  async function loadytClipper() {
    console.log('Loading yt clipper markup script');
    document.addEventListener('keydown', hotkeys, true);

    function hotkeys(e) {
      if (toggleKeys) {
        switch (e.code) {
          case 'KeyA':
            if (!e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              addMarker();
            } else if (e.shiftKey && markerHotkeysEnabled) {
              e.preventDefault();
              e.stopImmediatePropagation();
              moveMarker(enableMarkerHotkeys.endMarker);
            }

            break;

          case 'KeyS':
            if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              saveMarkersAndSettings();
            } else if (!e.ctrlKey && e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              $BHXf$export$copyToClipboard(getSettingsJSON());
            } else if (!e.ctrlKey && e.altKey && e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              saveAuthServerScript();
            }

            break;

          case 'KeyQ':
            if (!e.ctrlKey && !e.altKey && e.shiftKey && markerHotkeysEnabled) {
              e.preventDefault();
              e.stopImmediatePropagation();
              moveMarker(enableMarkerHotkeys.startMarker);
            } else if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              cyclePlayerSpeedDown();
            } else if (!e.ctrlKey && e.altKey && e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              updateAllMarkerPairSpeeds(settings.newMarkerSpeed);
            }

            break;

          case 'KeyE':
            if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              captureFrame();
            } else if (!e.ctrlKey && e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              saveCapturedFrames();
            }

            break;

          case 'KeyW':
            if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleGlobalSettingsEditor();
            } else if (!e.ctrlKey && e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleMarkerPairOverridesEditor();
            }

            break;

          case 'KeyC':
            if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleSpeedDucking();
            } else if (!e.ctrlKey && e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleMarkerLooping();
            } else if (!e.ctrlKey && !e.shiftKey && e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleGammaPreview();
            } else if (!e.ctrlKey && e.shiftKey && e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleFadeLoopPreview();
            } else if (e.ctrlKey && e.shiftKey && e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleAllPreviews();
            }

            break;

          case 'KeyG':
            if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              loadMarkers();
            }

            break;

          case 'KeyD':
            // alt+shift+D do not work in chrome 75.0.3770.100
            if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleSpeedChart();
            } else if (!e.ctrlKey && e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleSpeedMapLoop();
            } else if (!e.ctrlKey && !e.shiftKey && e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              resetSpeedMapLoop();
            }

            break;

          case 'KeyZ':
            if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              undoMarker();
            } else if (!e.ctrlKey && e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              redoMarker();
            } else if (!e.ctrlKey && !e.shiftKey && e.altKey && markerHotkeysEnabled) {
              e.preventDefault();
              e.stopImmediatePropagation();
              undoMarkerMove();
            } else if (!e.ctrlKey && e.shiftKey && e.altKey && markerHotkeysEnabled) {
              e.preventDefault();
              e.stopImmediatePropagation();
              redoMarkerMove();
            } else if (e.ctrlKey && e.shiftKey && e.altKey && markerHotkeysEnabled) {
              e.preventDefault();
              e.stopImmediatePropagation();
              deleteMarkerPair();
            }

            break;

          case 'KeyX':
            if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              drawCropOverlay(false);
            } else if (!e.ctrlKey && !e.altKey && e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              drawCropOverlay(true);
            } else if (!e.ctrlKey && e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleArrowKeyCropAdjustment();
            } else if (!e.ctrlKey && e.altKey && e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              updateAllMarkerPairCrops(settings.newMarkerCrop);
            } else if (e.ctrlKey && !e.altKey && !e.shiftKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              cycleCropDimOpacity();
            }

            break;

          case 'KeyV':
            if (!e.ctrlKey && !e.shiftKey && e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              sendGfyRequests(playerInfo.url);
            } else if (!e.ctrlKey && e.shiftKey && e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              requestGfycatAuth();
            }

            break;

          case 'KeyR':
            if (!e.ctrlKey && !e.shiftKey && !e.altKey && playerInfo.watchFlexy.theater) {
              e.preventDefault();
              e.stopImmediatePropagation();
              rotateVideo('clock');
            } else if (!e.ctrlKey && !e.shiftKey && e.altKey && playerInfo.watchFlexy.theater) {
              e.preventDefault();
              e.stopImmediatePropagation();
              rotateVideo('cclock');
            } else if (!e.ctrlKey && e.shiftKey && !e.altKey) {
              e.preventDefault();
              e.stopImmediatePropagation();
              toggleBigVideoPreviews();
            } else if (!e.ctrlKey && !e.shiftKey && !playerInfo.watchFlexy.theater) {
              e.preventDefault();
              e.stopImmediatePropagation();
              flashMessage('Please switch to theater mode to rotate video.', 'red');
            }

            break;

          case 'ArrowLeft':
          case 'ArrowRight':
            jumpToNearestMarkerOrPair(e, e.code);
            break;

          case 'ArrowUp':
            if (e.ctrlKey && !arrowKeyCropAdjustmentEnabled) {
              e.preventDefault();
              e.stopImmediatePropagation();
              togglePrevSelectedMarkerPair();
            }

            break;

          case 'ArrowDown':
            toggleAutoHideUnselectedMarkerPairs(e);
            break;
        }
      }

      if (!e.ctrlKey && e.shiftKey && e.altKey && e.code === 'KeyA') {
        toggleKeys = !toggleKeys;
        initOnce();

        if (toggleKeys) {
          showShortcutsTableToggleButton();
          flashMessage('Enabled Hotkeys', 'green');
        } else {
          hideShortcutsTableToggleButton();
          flashMessage('Disabled Hotkeys', 'red');
        }
      }
    }

    window.addEventListener('keydown', addCropOverlayHoverListener, true);
    window.addEventListener('keyup', removeCropOverlayHoverListener, true);

    function addCropOverlayHoverListener(e) {
      if (e.key === 'Control' && hotkeys && !e.repeat && isCropOverlayVisible && !isDrawingCrop && !isSpeedChartVisible) {
        window.addEventListener('mousemove', cropOverlayHoverHandler, true);
      }
    }

    function removeCropOverlayHoverListener(e) {
      if (e.key === 'Control') {
        window.removeEventListener('mousemove', cropOverlayHoverHandler, true);
        showPlayerControls();
        video.style.removeProperty('cursor');
      }
    }

    function cropOverlayHoverHandler(e) {
      if (isMarkerPairSettingsEditorOpen && isCropOverlayVisible && !isDrawingCrop) {
        updateCropHoverCursor(e);
      }
    }

    function updateCropHoverCursor(e) {
      const cursor = getMouseCropHoverRegion(e);

      if (cursor) {
        hidePlayerControls();
        video.style.cursor = cursor;
      } else {
        showPlayerControls();
        video.style.removeProperty('cursor');
      }
    }

    function togglePrevSelectedMarkerPair() {
      if (enableMarkerHotkeys.endMarker) {
        toggleMarkerPairEditor(enableMarkerHotkeys.endMarker);
      } else if (prevSelectedEndMarker) {
        toggleMarkerPairEditor(prevSelectedEndMarker);
      } else {
        const firstEndMarker = markersSvg.firstElementChild ? markersSvg.firstElementChild.nextElementSibling : null;
        if (firstEndMarker) toggleMarkerPairEditor(firstEndMarker);
      }
    }

    const CLIENT_ID = 'XXXX';
    const REDIRECT_URI = 'https://127.0.0.1:4443/yt_clipper';
    const BROWSER_BASED_AUTH_ENDPOINT = `https://gfycat.com/oauth/authorize?client_id=${CLIENT_ID}&scope=all&state=yt_clipper&response_type=token&redirect_uri=${REDIRECT_URI}`;
    let start = true;
    let markerHotkeysEnabled = false;
    let isMarkerPairSettingsEditorOpen = false;
    let wasGlobalSettingsEditorOpen = false;
    let isCropOverlayVisible = false;
    let isSpeedChartVisible = false;
    let checkGfysCompletedId;
    let markerPairs = [];
    let markerPairsHistory = [];
    let links = [];
    let startTime = 0.0;
    let toggleKeys = false;
    let prevSelectedEndMarker = null;
    let prevSelectedMarkerPairIndex = null;

    function init() {
      injectCSS($XbmT$var$ytClipperCSS, 'yt-clipper-css');
      initPlayerInfo();
      initMarkersContainer();
      addForeignEventListeners();
      injectToggleShortcutsTableButton();
      addCropOverlayDragListener();
    }

    function getMinWH() {
      const minWHMultiplier = Math.min(settings.cropResWidth, settings.cropResHeight) / 1080;
      const minW = Math.round(25 * minWHMultiplier);
      const minH = Math.round(25 * minWHMultiplier);
      return {
        minW,
        minH
      };
    }

    let isDraggingCrop = false;

    function addCropOverlayDragListener() {
      video.addEventListener('pointerdown', cropOverlayDragHandler, {
        capture: true
      });

      function cropOverlayDragHandler(e) {
        if (e.ctrlKey && isMarkerPairSettingsEditorOpen && isCropOverlayVisible && !isDrawingCrop && !isSpeedChartVisible) {
          const _getMinWH = getMinWH(),
                minW = _getMinWH.minW,
                minH = _getMinWH.minH;

          const _getCropComponents = getCropComponents(),
                _getCropComponents2 = $XbmT$var$_slicedToArray(_getCropComponents, 4),
                ix = _getCropComponents2[0],
                iy = _getCropComponents2[1],
                iw = _getCropComponents2[2],
                ih = _getCropComponents2[3];

          const cropAspectRatio = iw / ih;
          const videoRect = $XbmT$export$player.getVideoContentRect();
          const playerRect = $XbmT$export$player.getBoundingClientRect();
          const clickPosX = e.pageX - videoRect.left - playerRect.left;
          const clickPosY = e.pageY - videoRect.top - playerRect.top;
          const cursor = getMouseCropHoverRegion(e);
          let resizeHandler;

          if (!cursor) {
            return;
          } else {
            document.addEventListener('click', blockVideoPause, {
              once: true,
              capture: true
            });
            window.removeEventListener('mousemove', cropOverlayHoverHandler, true);
            window.removeEventListener('keydown', addCropOverlayHoverListener, true);
            window.removeEventListener('keyup', removeCropOverlayHoverListener, true);
            e.preventDefault();
            video.setPointerCapture(e.pointerId);
            document.addEventListener('pointerup', endCropOverlayDrag, {
              once: true,
              capture: true
            });
            isDraggingCrop = true;
            hidePlayerControls();

            if (cursor === 'grab') {
              video.style.cursor = 'grabbing';
              document.addEventListener('pointermove', dragCropHandler);
            } else {
              resizeHandler = e => getResizeHandler(e, cursor);

              document.addEventListener('pointermove', resizeHandler);
            }
          }

          function getResizeHandler(e, cursor) {
            const dragPosX = e.pageX - videoRect.left - playerRect.left;
            const changeX = dragPosX - clickPosX;
            let changeXScaled = changeX / videoRect.width * settings.cropResWidth;
            const dragPosY = e.pageY - videoRect.top - playerRect.top;
            const changeY = dragPosY - clickPosY;
            let changeYScaled = changeY / videoRect.height * settings.cropResHeight;
            const shouldMaintainCropAspectRatio = e.altKey;

            if (shouldMaintainCropAspectRatio && ['ne-resize', 'se-resize', 'sw-resize', 'nw-resize'].includes(cursor)) {
              if (Math.abs(changeXScaled) > Math.abs(changeYScaled)) {
                changeYScaled = changeXScaled / cropAspectRatio;
                if (['ne-resize', 'sw-resize'].includes(cursor)) changeYScaled = -changeYScaled;
              } else {
                changeXScaled = changeYScaled * cropAspectRatio;
                if (['ne-resize', 'sw-resize'].includes(cursor)) changeXScaled = -changeXScaled;
              }
            }

            let resizedDimensions;

            switch (cursor) {
              case 'n-resize':
                resizedDimensions = shouldMaintainCropAspectRatio ? getResizeNE(-changeYScaled * cropAspectRatio, changeYScaled) : getResizeN(changeYScaled);
                break;

              case 'ne-resize':
                resizedDimensions = getResizeNE(changeXScaled, changeYScaled);
                break;

              case 'e-resize':
                resizedDimensions = shouldMaintainCropAspectRatio ? getResizeSE(changeXScaled, changeXScaled / cropAspectRatio) : getResizeE(changeXScaled);
                break;

              case 'se-resize':
                resizedDimensions = getResizeSE(changeXScaled, changeYScaled);
                break;

              case 's-resize':
                resizedDimensions = shouldMaintainCropAspectRatio ? getResizeSE(changeYScaled * cropAspectRatio, changeYScaled) : getResizeS(changeYScaled);
                break;

              case 'sw-resize':
                resizedDimensions = getResizeSW(changeXScaled, changeYScaled);
                break;

              case 'w-resize':
                resizedDimensions = shouldMaintainCropAspectRatio ? getResizeSW(changeXScaled, -changeXScaled / cropAspectRatio) : getResizeW(changeXScaled);
                break;

              case 'nw-resize':
                resizedDimensions = getResizeNW(changeXScaled, changeYScaled);
                break;
            }

            const _resizedDimensions = resizedDimensions,
                  resizedX = _resizedDimensions.resizedX,
                  resizedY = _resizedDimensions.resizedY,
                  resizedW = _resizedDimensions.resizedW,
                  resizedH = _resizedDimensions.resizedH;
            updateCrop(resizedX, resizedY, resizedW, resizedH);
          }

          function dragCropHandler(e) {
            const dragPosX = e.pageX - videoRect.left - playerRect.left;
            const dragPosY = e.pageY - videoRect.top - playerRect.top;
            const changeX = dragPosX - clickPosX;
            const changeY = dragPosY - clickPosY;
            let X = Math.round(changeX / videoRect.width * settings.cropResWidth + ix);
            let Y = Math.round(changeY / videoRect.height * settings.cropResHeight + iy);
            X = $BHXf$export$clampNumber(X, 0, settings.cropResWidth - iw);
            Y = $BHXf$export$clampNumber(Y, 0, settings.cropResHeight - ih);
            updateCrop(X, Y, iw, ih);
          }

          function getResizeN(changeYScaled) {
            let Y = Math.round(iy + changeYScaled);
            let H = Math.round(ih - changeYScaled);
            Y = $BHXf$export$clampNumber(Y, 0, iy + ih - minH);
            H = $BHXf$export$clampNumber(H, minH, iy + ih);
            return {
              resizedX: ix,
              resizedY: Y,
              resizedW: iw,
              resizedH: H
            };
          }

          function getResizeE(changeXScaled) {
            let W = Math.round(iw + changeXScaled);
            W = $BHXf$export$clampNumber(W, minW, settings.cropResWidth - ix);
            return {
              resizedX: ix,
              resizedY: iy,
              resizedW: W,
              resizedH: ih
            };
          }

          function getResizeS(changeYScaled) {
            let H = Math.round(ih + changeYScaled);
            H = $BHXf$export$clampNumber(H, minH, settings.cropResHeight - iy);
            return {
              resizedX: ix,
              resizedY: iy,
              resizedW: iw,
              resizedH: H
            };
          }

          function getResizeW(changeXScaled) {
            let X = Math.round(ix + changeXScaled);
            let W = Math.round(iw - changeXScaled);
            X = $BHXf$export$clampNumber(X, 0, ix + iw - minW);
            W = $BHXf$export$clampNumber(W, minW, ix + iw);
            return {
              resizedX: X,
              resizedY: iy,
              resizedW: W,
              resizedH: ih
            };
          }

          function getResizeNE(changeXScaled, changeYScaled) {
            let Y = Math.round(iy + changeYScaled);
            let W = Math.round(iw + changeXScaled);
            let H = Math.round(ih - changeYScaled);
            Y = $BHXf$export$clampNumber(Y, 0, iy + ih - minH);
            W = $BHXf$export$clampNumber(W, minW, settings.cropResWidth - ix);
            H = $BHXf$export$clampNumber(H, minH, iy + ih);
            return {
              resizedX: ix,
              resizedY: Y,
              resizedW: W,
              resizedH: H
            };
          }

          function getResizeSE(changeXScaled, changeYScaled) {
            let W = Math.round(iw + changeXScaled);
            let H = Math.round(ih + changeYScaled);
            W = $BHXf$export$clampNumber(W, minW, settings.cropResWidth - ix);
            H = $BHXf$export$clampNumber(H, minH, settings.cropResHeight - iy);
            return {
              resizedX: ix,
              resizedY: iy,
              resizedW: W,
              resizedH: H
            };
          }

          function getResizeSW(changeXScaled, changeYScaled) {
            let X = Math.round(ix + changeXScaled);
            let W = Math.round(iw - changeXScaled);
            let H = Math.round(ih + changeYScaled);
            X = $BHXf$export$clampNumber(X, 0, ix + iw - minW);
            W = $BHXf$export$clampNumber(W, minW, ix + iw);
            H = $BHXf$export$clampNumber(H, minH, settings.cropResHeight - iy);
            return {
              resizedX: X,
              resizedY: iy,
              resizedW: W,
              resizedH: H
            };
          }

          function getResizeNW(changeXScaled, changeYScaled) {
            let X = Math.round(ix + changeXScaled);
            let W = Math.round(iw - changeXScaled);
            let Y = Math.round(iy + changeYScaled);
            let H = Math.round(ih - changeYScaled);
            X = $BHXf$export$clampNumber(X, 0, ix + iw - minW);
            W = $BHXf$export$clampNumber(W, minW, ix + iw);
            Y = $BHXf$export$clampNumber(Y, 0, iy + ih - minH);
            H = $BHXf$export$clampNumber(H, minH, iy + ih);
            return {
              resizedX: X,
              resizedY: Y,
              resizedW: W,
              resizedH: H
            };
          }

          function endCropOverlayDrag(e) {
            isDraggingCrop = false;
            video.releasePointerCapture(e.pointerId);
            cropInput.dispatchEvent(new Event('change'));
            cursor === 'grab' ? document.removeEventListener('pointermove', dragCropHandler) : document.removeEventListener('pointermove', resizeHandler);
            showPlayerControls();

            if (e.ctrlKey) {
              if (cursor) video.style.cursor = cursor;
              updateCropHoverCursor(e);
              window.addEventListener('mousemove', cropOverlayHoverHandler, true);
              window.addEventListener('keyup', removeCropOverlayHoverListener, true);
              window.addEventListener('keydown', addCropOverlayHoverListener, true);
            } else {
              video.style.removeProperty('cursor');
              window.addEventListener('keydown', addCropOverlayHoverListener, true);
            }
          }
        }
      }
    }

    function getMouseCropHoverRegion(e) {
      const _getCropComponents3 = getCropComponents(),
            _getCropComponents4 = $XbmT$var$_slicedToArray(_getCropComponents3, 4),
            x = _getCropComponents4[0],
            y = _getCropComponents4[1],
            w = _getCropComponents4[2],
            h = _getCropComponents4[3];

      const videoRect = $XbmT$export$player.getVideoContentRect();
      const playerRect = $XbmT$export$player.getBoundingClientRect();
      const clickPosX = e.pageX - videoRect.left - playerRect.left;
      const clickPosY = e.pageY - videoRect.top - playerRect.top;
      const clickPosXScaled = clickPosX / videoRect.width * settings.cropResWidth;
      const clickPosYScaled = clickPosY / videoRect.height * settings.cropResHeight;
      const slMultiplier = Math.min(settings.cropResWidth, settings.cropResHeight) / 1080;
      const sl = Math.ceil(Math.min(w, h) * slMultiplier * 0.1);
      const edgeOffset = 30 * slMultiplier;
      let cursor;
      let mouseCropColumn;

      if (x - edgeOffset < clickPosXScaled && clickPosXScaled < x + sl) {
        mouseCropColumn = 1;
      } else if (x + sl < clickPosXScaled && clickPosXScaled < x + w - sl) {
        mouseCropColumn = 2;
      } else if (x + w - sl < clickPosXScaled && clickPosXScaled < x + w + edgeOffset) {
        mouseCropColumn = 3;
      }

      let mouseCropRow;

      if (y - edgeOffset < clickPosYScaled && clickPosYScaled < y + sl) {
        mouseCropRow = 1;
      } else if (y + sl < clickPosYScaled && clickPosYScaled < y + h - sl) {
        mouseCropRow = 2;
      } else if (y + h - sl < clickPosYScaled && clickPosYScaled < y + h + edgeOffset) {
        mouseCropRow = 3;
      }

      const isMouseInCropCenter = mouseCropColumn === 2 && mouseCropRow === 2;
      const isMouseInCropN = mouseCropColumn === 2 && mouseCropRow === 1;
      const isMouseInCropNE = mouseCropColumn === 3 && mouseCropRow === 1;
      const isMouseInCropE = mouseCropColumn === 3 && mouseCropRow === 2;
      const isMouseInCropSE = mouseCropColumn === 3 && mouseCropRow === 3;
      const isMouseInCropS = mouseCropColumn === 2 && mouseCropRow === 3;
      const isMouseInCropSW = mouseCropColumn === 1 && mouseCropRow === 3;
      const isMouseInCropW = mouseCropColumn === 1 && mouseCropRow === 2;
      const isMouseInCropNW = mouseCropColumn === 1 && mouseCropRow === 1;
      if (isMouseInCropCenter) cursor = 'grab';
      if (isMouseInCropN) cursor = 'n-resize';
      if (isMouseInCropNE) cursor = 'ne-resize';
      if (isMouseInCropE) cursor = 'e-resize';
      if (isMouseInCropSE) cursor = 'se-resize';
      if (isMouseInCropS) cursor = 's-resize';
      if (isMouseInCropSW) cursor = 'sw-resize';
      if (isMouseInCropW) cursor = 'w-resize';
      if (isMouseInCropNW) cursor = 'nw-resize';
      return cursor;
    }

    const initOnce = $BHXf$export$once(init, this);
    $XbmT$export$player = await $BHXf$export$retryUntilTruthyResult(() => document.getElementById('movie_player'));
    $XbmT$exports.player = $XbmT$export$player;
    const playerInfo = {};
    const video = await $BHXf$export$retryUntilTruthyResult(() => document.getElementsByTagName('video')[0]);
    let settingsEditorHook;
    let flashMessageHook;
    let overlayHook;

    function initPlayerInfo() {
      playerInfo.url = $XbmT$export$player.getVideoUrl();
      playerInfo.playerData = $XbmT$export$player.getVideoData();
      playerInfo.duration = $XbmT$export$player.getDuration();
      playerInfo.video = document.getElementsByTagName('video')[0];
      playerInfo.video.setAttribute('id', 'yt-clipper-video');
      playerInfo.aspectRatio = $XbmT$export$player.getVideoAspectRatio();
      playerInfo.isVerticalVideo = playerInfo.aspectRatio <= 1;
      playerInfo.progress_bar = document.getElementsByClassName('ytp-progress-bar')[0];
      playerInfo.watchFlexy = document.getElementsByTagName('ytd-watch-flexy')[0];
      playerInfo.infoContents = document.getElementById('info-contents');
      playerInfo.container = document.querySelector('#ytd-player #container');
      flashMessageHook = playerInfo.infoContents;
      playerInfo.columns = document.getElementById('columns');
      updateSettingsEditorHook();
      playerInfo.annotations = document.getElementsByClassName('ytp-iv-video-content')[0];
      overlayHook = document.getElementsByClassName('html5-video-container')[0];
      playerInfo.controls = document.getElementsByClassName('ytp-chrome-bottom')[0];
      playerInfo.controlsBar = document.getElementsByClassName('ytp-chrome-controls')[0];
      playerInfo.progressBar = document.getElementsByClassName('ytp-progress-bar-container')[0];
      playerInfo.gradientBottom = document.getElementsByClassName('ytp-gradient-bottom')[0];
    }

    function updateSettingsEditorHook() {
      if (playerInfo.watchFlexy.theater) {
        settingsEditorHook = playerInfo.columns;
      } else {
        settingsEditorHook = playerInfo.infoContents;
      }
    }

    document.body.addEventListener('wheel', mouseWheelFrameSkipHandler);

    function mouseWheelFrameSkipHandler(event) {
      if (toggleKeys && !event.ctrlKey && !event.altKey && event.shiftKey && Math.abs(event.deltaY) > 0) {
        let fps = getFPS();

        if (event.deltaY < 0) {
          $XbmT$export$player.seekBy(1 / fps);
        } else if (event.deltaY > 0) {
          $XbmT$export$player.seekBy(-1 / fps);
        }
      }
    }

    document.body.addEventListener('wheel', markerFrameSkipHandler);

    function markerFrameSkipHandler(event) {
      if (toggleKeys && !event.ctrlKey && event.altKey && event.shiftKey && Math.abs(event.deltaY) > 0 && isMarkerPairSettingsEditorOpen && !wasGlobalSettingsEditorOpen && prevSelectedEndMarker) {
        const fps = getFPS();
        let targetMarker = prevSelectedEndMarker;
        const markerPair = markerPairs[prevSelectedMarkerPairIndex];
        let targetMarkerTime = markerPair.end;

        if (event.pageX < window.innerWidth / 2) {
          targetMarker = prevSelectedEndMarker.previousElementSibling;
          targetMarkerTime = markerPair.start;
        }

        if (event.deltaY > 0) {
          moveMarker(targetMarker, Math.max(0, targetMarkerTime - 1 / fps));
        } else if (event.deltaY < 0) {
          moveMarker(targetMarker, Math.min(video.duration, targetMarkerTime + 1 / fps));
        }
      }
    }

    let settings;
    let markersSvg;
    let markersDiv;
    let selectedMarkerPairOverlay;
    let startMarkerNumberings;
    let endMarkerNumberings;

    function initMarkersContainer() {
      settings = {
        videoID: playerInfo.playerData.video_id,
        videoTitle: playerInfo.playerData.title,
        newMarkerSpeed: 1.0,
        newMarkerCrop: '0:0:iw:ih',
        titleSuffix: `[${playerInfo.playerData.video_id}]`,
        isVerticalVideo: playerInfo.isVerticalVideo,
        cropRes: playerInfo.isVerticalVideo ? '1080x1920' : '1920x1080',
        cropResWidth: playerInfo.isVerticalVideo ? 1080 : 1920,
        cropResHeight: playerInfo.isVerticalVideo ? 1920 : 1080,
        markerPairMergeList: ''
      };
      markersDiv = document.createElement('div');
      markersDiv.setAttribute('id', 'markers-div');
      markersDiv.innerHTML = `\
    <svg id="markers-svg"></svg>
    <svg id="selected-marker-pair-overlay" style="display:none">
      <rect id="selected-start-marker-overlay"  class="selected-marker-overlay" width="1px" height="8px" y="3.5px" shape-rendering="crispEdges"></rect>
      <rect id="selected-end-marker-overlay"  class="selected-marker-overlay" width="1px" height="8px" y="3.5px" shape-rendering="crispEdges"></rect>
    </svg>
    <svg id="start-marker-numberings"></svg>
    <svg id="end-marker-numberings"></svg>
    `;
      playerInfo.progress_bar.appendChild(markersDiv);
      markersSvg = markersDiv.children[0];
      selectedMarkerPairOverlay = markersDiv.children[1];
      startMarkerNumberings = markersDiv.children[2];
      endMarkerNumberings = markersDiv.children[3];
    }

    function injectCSS(css, id) {
      const style = document.createElement('style');
      style.setAttribute('id', id);
      style.innerHTML = css;
      document.body.appendChild(style);
      return style;
    }

    const adjustRotatedVideoPositionCSS = `\
    @media (min-aspect-ratio: 29/18) {
      #yt-clipper-video {
        margin-left: 36%;
      }
    }
    @media (min-aspect-ratio: 40/18) {
      #yt-clipper-video {
        margin-left: 25%;
      }
    }
    @media (max-aspect-ratio: 29/18) {
      #yt-clipper-video {
        margin-left: 34%;
      }
    }
    @media (max-aspect-ratio: 13/9) {
      #yt-clipper-video {
        margin-left: 32%;
      }
    }
    @media (max-aspect-ratio: 23/18) {
      #yt-clipper-video {
        margin-left: 30%;
      }
    }
    @media (max-aspect-ratio: 10/9) {
      #yt-clipper-video {
        margin-left: 26%;
      }
    }
    @media (max-aspect-ratio: 17/18) {
      #yt-clipper-video {
        margin-left: 22%;
      }
    }
    @media (max-aspect-ratio: 7/9) {
      #yt-clipper-video {
        margin-left: 16%;
      }
    }
    @media (max-aspect-ratio: 6/9) {
      #yt-clipper-video {
        margin-left: 14%;
      }
    }
    @media (max-aspect-ratio: 11/18) {
      #yt-clipper-video {
        margin-left: 10%;
      }
    }
    @media (max-aspect-ratio: 5/9) {
      #yt-clipper-video {
        margin-left: 0%;
      }
    }
    `;
    let rotatedVideoCSS;
    let fullscreenRotatedVideoCSS;
    let rotatedVideoPreviewsCSS;
    let rotatedVideoStyle;
    let adjustRotatedVideoPositionStyle;
    let fullscreenRotatedVideoStyle;
    let rotatedVideoPreviewsStyle;
    let rotation = 0;

    function rotateVideo(direction) {
      if (direction === 'clock') {
        rotation = rotation === 0 ? 90 : 0;
      } else if (direction === 'cclock') {
        rotation = rotation === 0 ? -90 : 0;
      }

      if (rotation === 90 || rotation === -90) {
        let scale = 1;
        scale = 1 / playerInfo.aspectRatio;
        rotatedVideoCSS = `
        #yt-clipper-video {
          transform: rotate(${rotation}deg) scale(2.2) !important;
          max-width: 45vh;
          max-height: 100vw;
        }
        #player-theater-container {
          height: 100vh !important;
          max-height: none !important;
        }
        #page-manager {
          margin-top: 0px !important;
        }
        #masthead #container {
          display: none !important;
        }
      `;
        rotatedVideoPreviewsCSS = `\
        .ytp-tooltip {
          transform: translateY(-20%) rotate(${rotation}deg) !important;
        }
        .ytp-tooltip-text-wrapper {
          transform: rotate(${-rotation}deg) !important;
          opacity: 0.6;
        }
      `;
        fullscreenRotatedVideoCSS = `
      #yt-clipper-video {
        transform: rotate(${rotation}deg) scale(${scale}) !important;
        margin-left: auto;
      }
      `;

        if (!document.fullscreen) {
          adjustRotatedVideoPositionStyle = injectCSS(adjustRotatedVideoPositionCSS, 'adjust-rotated-video-position-css');
          rotatedVideoStyle = injectCSS(rotatedVideoCSS, 'yt-clipper-rotate-video-css');
          window.dispatchEvent(new Event('resize'));
        } else {
          fullscreenRotatedVideoStyle = injectCSS(fullscreenRotatedVideoCSS, 'fullscreen-rotated-video-css');
        }

        rotatedVideoPreviewsStyle = injectCSS(rotatedVideoPreviewsCSS, 'yt-clipper-rotated-video-previews-css');
        $BHXf$export$deleteElement(bigVideoPreviewsStyle);
        bigVideoPreviewsStyle = null;
        window.dispatchEvent(new Event('resize'));
        document.addEventListener('fullscreenchange', fullscreenRotateVideoHandler);
      } else {
        $BHXf$export$deleteElement(rotatedVideoStyle);
        $BHXf$export$deleteElement(adjustRotatedVideoPositionStyle);
        $BHXf$export$deleteElement(fullscreenRotatedVideoStyle);
        $BHXf$export$deleteElement(rotatedVideoPreviewsStyle);
        $BHXf$export$deleteElement(bigVideoPreviewsStyle);
        bigVideoPreviewsStyle = null;
        window.dispatchEvent(new Event('resize'));
        document.removeEventListener('fullscreenchange', fullscreenRotateVideoHandler);
      }
    }

    function fullscreenRotateVideoHandler() {
      if (document.fullscreen) {
        $BHXf$export$deleteElement(rotatedVideoStyle);
        $BHXf$export$deleteElement(adjustRotatedVideoPositionStyle);
        fullscreenRotatedVideoStyle = injectCSS(fullscreenRotatedVideoCSS, 'fullscreen-rotated-video-css');
      } else {
        $BHXf$export$deleteElement(fullscreenRotatedVideoStyle);
        adjustRotatedVideoPositionStyle = injectCSS(adjustRotatedVideoPositionCSS, 'adjust-rotated-video-position-css');
        rotatedVideoStyle = injectCSS(rotatedVideoCSS, 'yt-clipper-rotate-video-css');
        document.removeEventListener('fullscreenchange', fullscreenRotateVideoHandler);
        window.dispatchEvent(new Event('resize'));
      }
    }

    let bigVideoPreviewsStyle;

    function toggleBigVideoPreviews() {
      const bigVideoPreviewsCSS = `\
    .ytp-tooltip {
      left: 45% !important;
      transform: ${rotation ? `translateY(-285%) rotate(${rotation}deg)` : 'translateY(-160%) '} scale(4) !important;
      padding: 1px !important;
      border-radius: 1px !important;
    }
    .ytp-tooltip-text-wrapper {
      transform: scale(0.5) ${rotation ? `rotate(${-rotation}deg)` : ''}!important;
      opacity: 0.6;
    }
    `;

      if (bigVideoPreviewsStyle) {
        $BHXf$export$deleteElement(bigVideoPreviewsStyle);
        bigVideoPreviewsStyle = null;
      } else {
        bigVideoPreviewsStyle = injectCSS(bigVideoPreviewsCSS, 'yt-clipper-big-video-previews-css');
      }
    }

    function addForeignEventListeners() {
      const ids = ['search'];
      ids.forEach(id => {
        const input = document.getElementById(id);

        if (toggleKeys) {
          input.addEventListener('focus', () => toggleKeys = false, {
            capture: true
          });
          input.addEventListener('blur', () => toggleKeys = true, {
            capture: true
          });
        }
      });
    }

    function flashMessage(msg, color) {
      let lifetime = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 2500;
      const flashDiv = document.createElement('div');
      flashDiv.setAttribute('class', 'msg-div flash-div');
      flashDiv.innerHTML = `<span class="flash-msg" style="color:${color}">${msg}</span>`;
      flashMessageHook.insertAdjacentElement('beforebegin', flashDiv);
      setTimeout(() => $BHXf$export$deleteElement(flashDiv), lifetime);
    }

    function getShortestActiveMarkerPair() {
      let currentTime = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : video.currentTime;

      if (isMarkerPairSettingsEditorOpen && !wasGlobalSettingsEditorOpen && prevSelectedMarkerPairIndex != null) {
        const selectedMarkerPair = markerPairs[prevSelectedMarkerPairIndex];

        if (currentTime >= Math.floor(selectedMarkerPair.start * 1e6) / 1e6 && currentTime <= Math.ceil(selectedMarkerPair.end * 1e6) / 1e6) {
          return selectedMarkerPair;
        }
      }

      const activeMarkerPairs = markerPairs.filter(markerPair => {
        if (currentTime >= Math.floor(markerPair.start * 1e6) / 1e6 && currentTime <= Math.ceil(markerPair.end * 1e6) / 1e6) {
          return true;
        }

        return false;
      });

      if (activeMarkerPairs.length === 0) {
        return null;
      }

      const shortestActiveMarkerPair = activeMarkerPairs.reduce((prev, cur) => {
        if (cur.end - cur.start < prev.end - prev.start) {
          return cur;
        }

        return prev;
      });
      return shortestActiveMarkerPair;
    }

    let isSpeedPreviewOn = false;

    const toggleSpeedDucking = () => {
      if (isSpeedPreviewOn) {
        isSpeedPreviewOn = false;
        flashMessage('Auto speed ducking disabled', 'red');
      } else {
        isSpeedPreviewOn = true;
        requestAnimationFrame(updateSpeed);
        flashMessage('Auto speed ducking enabled', 'green');
      }
    };

    let prevSpeed = 1;
    const defaultRoundSpeedMapEasing = 0.05;

    function updateSpeed() {
      const shortestActiveMarkerPair = getShortestActiveMarkerPair();

      if (shortestActiveMarkerPair) {
        let markerPairSpeed;
        const enableSpeedMaps = shortestActiveMarkerPair.overrides.enableSpeedMaps !== undefined ? shortestActiveMarkerPair.overrides.enableSpeedMaps : settings.enableSpeedMaps !== false;

        if (enableSpeedMaps) {
          markerPairSpeed = getSpeedMapping(shortestActiveMarkerPair.speedMap, video.currentTime, defaultRoundSpeedMapEasing, 2);
        } else {
          markerPairSpeed = shortestActiveMarkerPair.speed;
        } // console.log(markerPairSpeed);


        if (prevSpeed !== markerPairSpeed) {
          $XbmT$export$player.setPlaybackRate(markerPairSpeed);
          prevSpeed = markerPairSpeed;
        }
      } else if (prevSpeed !== 1) {
        $XbmT$export$player.setPlaybackRate(1);
        prevSpeed = 1;
      }

      if (isSpeedPreviewOn) {
        requestAnimationFrame(updateSpeed);
      } else {
        $XbmT$export$player.setPlaybackRate(1);
        prevSpeed = 1;
      }
    }

    function getSpeedMapping(speedMap, time) {
      let roundMultiple = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : defaultRoundSpeedMapEasing;
      let roundPrecision = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2;
      let len = speedMap.length;

      if (len === 2 && speedMap[0].y === speedMap[1].y) {
        return speedMap[0].y;
      }

      len--;
      let left;
      let right;

      for (let i = 0; i < len; ++i) {
        if (speedMap[i].x <= time && time <= speedMap[i + 1].x) {
          left = speedMap[i];
          right = speedMap[i + 1];
          break;
        }
      }

      if (left && right) {
        if (left.y === right.y) {
          return left.y;
        }

        const elapsed = video.currentTime - left.x;
        const duration = right.x - left.x;
        let easedTimePercentage;

        if (easingMode === 'cubicInOut') {
          easedTimePercentage = $ATE0$export$cubicInOut(elapsed / duration);
        } else if (easingMode === 'linear') {
          easedTimePercentage = elapsed / duration;
        }

        const change = right.y - left.y;
        const rawSpeed = left.y + change * easedTimePercentage || right.y;
        const roundedSpeed = roundMultiple > 0 ? $BHXf$export$roundValue(rawSpeed, roundMultiple, roundPrecision) : rawSpeed; // console.log(roundedSpeed);

        return roundedSpeed;
      } else {
        return 1;
      }
    }

    let isMarkerLoopPreviewOn = false;

    function toggleMarkerLooping() {
      if (isMarkerLoopPreviewOn) {
        isMarkerLoopPreviewOn = false;
        flashMessage('Auto marker looping disabled', 'red');
      } else {
        isMarkerLoopPreviewOn = true;
        requestAnimationFrame(loopMarkerPair);
        flashMessage('Auto marker looping enabled', 'green');
      }
    }

    function loopMarkerPair() {
      if (isMarkerPairSettingsEditorOpen && !wasGlobalSettingsEditorOpen) {
        if (prevSelectedMarkerPairIndex != null) {
          const markerPair = markerPairs[prevSelectedMarkerPairIndex];

          if (markerPair.speedMapLoop.enabled && markerPair.speedMapLoop.start > markerPair.start && markerPair.speedMapLoop.end < markerPair.end && markerPair.speedMapLoop.start < markerPair.speedMapLoop.end) {
            const isTimeBetweenSpeedMapLoop = markerPair.speedMapLoop.start <= video.currentTime && video.currentTime <= markerPair.speedMapLoop.end;

            if (!isTimeBetweenSpeedMapLoop) {
              $XbmT$export$player.seekTo(markerPair.speedMapLoop.start);
            }
          } else {
            const isTimeBetweenMarkerPair = markerPair.start <= video.currentTime && video.currentTime <= markerPair.end;

            if (!isTimeBetweenMarkerPair) {
              $XbmT$export$player.seekTo(markerPair.start);
            }
          }
        }
      }

      if (isMarkerLoopPreviewOn) {
        requestAnimationFrame(loopMarkerPair);
      }
    }

    let gammaFilterDiv;
    let isGammaPreviewOn = false;
    let gammaR;
    let gammaG;
    let gammaB;
    let gammaFilterSvg;

    function toggleGammaPreview() {
      if (!gammaFilterDiv) {
        gammaFilterDiv = document.createElement('div');
        gammaFilterDiv.setAttribute('id', 'gamma-filter-div');
        gammaFilterDiv.innerHTML = `\
      <svg id="gamma-filter-svg" xmlns="http://www.w3.org/2000/svg" width="0" height="0">
        <defs>
          <filter id="gamma-filter">
            <feComponentTransfer id="gamma-filter-comp-transfer">
              <feFuncR id="gamma-r" type="gamma" offset="0" amplitude="1"></feFuncR>
              <feFuncG id="gamma-g" type="gamma" offset="0" amplitude="1"></feFuncG>
              <feFuncB id="gamma-b" type="gamma" offset="0" amplitude="1"></feFuncB>
            </feComponentTransfer>
          </filter>
        </defs>
      </svg>
      `;
        document.body.appendChild(gammaFilterDiv);
        gammaFilterSvg = gammaFilterDiv.firstElementChild;
        gammaR = document.getElementById('gamma-r');
        gammaG = document.getElementById('gamma-g');
        gammaB = document.getElementById('gamma-b');
      }

      if (!isGammaPreviewOn) {
        video.style.filter = 'url(#gamma-filter)';
        isGammaPreviewOn = true;
        requestAnimationFrame(gammaPreviewHandler);
        flashMessage('Gamma preview enabled', 'green');
      } else {
        video.style.filter = null;
        isGammaPreviewOn = false;
        flashMessage('Gamma preview disabled', 'red');
      }
    }

    let prevGammaVal = 1;

    function gammaPreviewHandler() {
      const shortestActiveMarkerPair = getShortestActiveMarkerPair();

      if (shortestActiveMarkerPair) {
        const markerPairGamma = shortestActiveMarkerPair.overrides.gamma || settings.gamma || 1;

        if (prevGammaVal !== markerPairGamma) {
          // console.log(`Updating gamma from ${prevGammaVal} to ${markerPairGamma}`);
          gammaR.exponent.baseVal = markerPairGamma;
          gammaG.exponent.baseVal = markerPairGamma;
          gammaB.exponent.baseVal = markerPairGamma; // force re-render of filter (possible bug with chrome and other browsers?)

          gammaFilterSvg.setAttribute('width', '0');
          prevGammaVal = markerPairGamma;
        }
      } else {
        if (prevGammaVal !== 1) {
          // console.log(`Updating gamma from ${prevGammaVal} to 1`);
          gammaR.exponent.baseVal = 1;
          gammaG.exponent.baseVal = 1;
          gammaB.exponent.baseVal = 1;
          gammaFilterSvg.setAttribute('width', '0');
          prevGammaVal = 1;
        }
      }

      if (isGammaPreviewOn) {
        requestAnimationFrame(gammaPreviewHandler);
      }
    }

    let isFadeLoopPreviewOn = false;

    function toggleFadeLoopPreview() {
      if (!isFadeLoopPreviewOn) {
        isFadeLoopPreviewOn = true;
        requestAnimationFrame(fadeLoopPreviewHandler);
        flashMessage('Fade loop preview enabled', 'green');
      } else {
        isFadeLoopPreviewOn = false;
        video.style.opacity = '1';
        flashMessage('Fade loop preview disabled', 'red');
      }
    }

    function fadeLoopPreviewHandler() {
      const currentTime = video.currentTime;
      const shortestActiveMarkerPair = getShortestActiveMarkerPair();

      if (shortestActiveMarkerPair && (shortestActiveMarkerPair.overrides.loop === 'fade' || shortestActiveMarkerPair.overrides.loop == null && settings.loop === 'fade')) {
        const currentTimeP = getFadeBounds(shortestActiveMarkerPair, currentTime);

        if (currentTimeP == null) {
          video.style.opacity = '1';
        } else {
          let currentTimeEased = $ATE0$export$cubicInOut(currentTimeP);
          video.style.opacity = currentTimeEased.toString(); // console.log(video.style.opacity);
        }
      } else {
        video.style.opacity = '1';
      }

      isFadeLoopPreviewOn ? requestAnimationFrame(fadeLoopPreviewHandler) : video.style.opacity = '1';
    }

    function getFadeBounds(markerPair, currentTime) {
      const start = Math.floor(markerPair.start * 1e6) / 1e6;
      const end = Math.ceil(markerPair.end * 1e6) / 1e6;
      const inputDuration = end - start;
      const outputDuration = markerPair.outputDuration;
      let fadeDuration = markerPair.overrides.fadeDuration || settings.fadeDuration || 0.5;
      fadeDuration = Math.min(fadeDuration, 0.4 * outputDuration);
      const fadeInStartP = 0;
      const fadeInEndP = fadeDuration / outputDuration;
      const fadeOutStartP = (outputDuration - fadeDuration) / outputDuration;
      const fadeOutEndP = outputDuration / outputDuration;
      let currentTimeP = (currentTime - start) / inputDuration;

      if (currentTimeP >= fadeInStartP && currentTimeP <= fadeInEndP) {
        currentTimeP = (currentTime - start) / fadeDuration;
        return currentTimeP;
      } else if (currentTimeP >= fadeOutStartP && currentTimeP <= fadeOutEndP) {
        currentTimeP = 1 - (currentTime - start - (inputDuration - fadeDuration)) / fadeDuration;
        return currentTimeP;
      } else {
        return null;
      }
    }

    let isAllPreviewsOn = false;

    function toggleAllPreviews() {
      isAllPreviewsOn = isSpeedPreviewOn && isMarkerLoopPreviewOn && isGammaPreviewOn && isFadeLoopPreviewOn;

      if (!isAllPreviewsOn) {
        !isSpeedPreviewOn && toggleSpeedDucking();
        !isMarkerLoopPreviewOn && toggleMarkerLooping();
        !isGammaPreviewOn && toggleGammaPreview();
        !isFadeLoopPreviewOn && toggleFadeLoopPreview();
        isAllPreviewsOn = true;
      } else {
        isSpeedPreviewOn && toggleSpeedDucking();
        isMarkerLoopPreviewOn && toggleMarkerLooping();
        isGammaPreviewOn && toggleGammaPreview();
        isFadeLoopPreviewOn && toggleFadeLoopPreview();
        isAllPreviewsOn = false;
      }
    }

    function jumpToNearestMarkerOrPair(e, keyCode) {
      if (!arrowKeyCropAdjustmentEnabled) {
        if (e.ctrlKey && !e.altKey && !e.shiftKey) {
          jumpToNearestMarker(e, video.currentTime, keyCode);
        } else if (e.altKey && !e.shiftKey) {
          if (!e.ctrlKey && !(isMarkerPairSettingsEditorOpen && !wasGlobalSettingsEditorOpen)) {
            e.preventDefault();
            e.stopImmediatePropagation();
            togglePrevSelectedMarkerPair();
          }

          if (enableMarkerHotkeys.endMarker) {
            jumpToNearestMarkerPair(e, enableMarkerHotkeys.endMarker, keyCode);
          }
        }
      }
    }

    function jumpToNearestMarkerPair(e, targetEndMarker, keyCode) {
      e.preventDefault();
      e.stopImmediatePropagation();
      let index = parseInt(targetEndMarker.getAttribute('idx')) - 1;

      if (keyCode === 'ArrowLeft' && index > 0) {
        targetEndMarker = enableMarkerHotkeys.endMarker.previousElementSibling.previousElementSibling;
        targetEndMarker && toggleMarkerPairEditor(targetEndMarker);

        if (e.ctrlKey) {
          index--;
          $XbmT$export$player.seekTo(markerPairs[index].start);
        }
      } else if (keyCode === 'ArrowRight' && index < markerPairs.length - 1) {
        targetEndMarker = enableMarkerHotkeys.endMarker.nextElementSibling.nextElementSibling;
        targetEndMarker && toggleMarkerPairEditor(targetEndMarker);

        if (e.ctrlKey) {
          index++;
          $XbmT$export$player.seekTo(markerPairs[index].start);
        }
      }
    }

    let dblJump = 0;
    let prevJumpKeyCode;
    let prevTime;

    function jumpToNearestMarker(e, currentTime, keyCode) {
      e.preventDefault();
      e.stopImmediatePropagation();
      let minTime;
      currentTime = prevTime != null ? prevTime : currentTime;
      let markerTimes = [];
      markerPairs.forEach(markerPair => {
        markerTimes.push(markerPair.start);
        markerTimes.push(markerPair.end);
      });

      if (start === false) {
        markerTimes.push(startTime);
      }

      markerTimes = markerTimes.map(markerTime => parseFloat(markerTime.toFixed(6)));

      if (keyCode === 'ArrowLeft') {
        markerTimes = markerTimes.filter(markerTime => markerTime < currentTime);
        minTime = Math.max(...markerTimes);

        if (dblJump != 0 && markerTimes.length > 0 && prevJumpKeyCode === 'ArrowLeft') {
          markerTimes = markerTimes.filter(markerTime => markerTime < minTime);
          minTime = Math.max(...markerTimes);
        }

        prevJumpKeyCode = 'ArrowLeft';
      } else if (keyCode === 'ArrowRight') {
        markerTimes = markerTimes.filter(markerTime => markerTime > currentTime);
        minTime = Math.min(...markerTimes);

        if (dblJump != 0 && markerTimes.length > 0 && prevJumpKeyCode === 'ArrowRight') {
          markerTimes = markerTimes.filter(markerTime => markerTime > minTime);
          minTime = Math.min(...markerTimes);
        }

        prevJumpKeyCode = 'ArrowRight';
      }

      if (dblJump !== 0) {
        clearTimeout(dblJump);
        dblJump = 0;
        prevTime = null;
        if (minTime !== currentTime && minTime != Infinity && minTime != -Infinity) $XbmT$export$player.seekTo(minTime);
      } else {
        prevTime = currentTime;
        if (minTime !== currentTime && minTime != Infinity && minTime != -Infinity) $XbmT$export$player.seekTo(minTime);
        dblJump = setTimeout(() => {
          dblJump = 0;
          prevTime = null;
        }, 150);
      }
    }

    function saveMarkersAndSettings() {
      const settingsJSON = getSettingsJSON();
      const blob = new Blob([settingsJSON], {
        type: 'text/plain;charset=utf-8'
      });
      $oSxG$exports.saveAs(blob, `${settings.titleSuffix || `[${settings.videoID}]`}.json`);
    }

    function getSettingsJSON() {
      markerPairs.forEach((markerPair, index) => {
        const speed = markerPair.speed;

        if (typeof speed === 'string') {
          markerPair.speed = Number(speed);
          console.log(`Converted marker pair ${index}'s speed from String to Number`);
        }
      });
      const markerPairsNumbered = markerPairs.map((markerPair, idx) => {
        const markerPairNumbered = Object.assign(Object.assign({
          number: idx + 1
        }, markerPair), {
          speedMapLoop: undefined,
          speedMap: isVariableSpeed(markerPair.speedMap) ? markerPair.speedMap : undefined,
          startNumbering: undefined,
          endNumbering: undefined,
          moveHistory: undefined
        });
        return markerPairNumbered;
      });
      const settingsJSON = JSON.stringify(Object.assign(Object.assign({}, settings), {
        version: $XbmT$var$__version__,
        markerPairs: markerPairsNumbered
      }), undefined, 2);
      return settingsJSON;
    }

    function isVariableSpeed(speedMap) {
      if (speedMap.length < 2) return false;
      let isVariableSpeed = speedMap.some((speedPoint, i) => {
        if (i === speedMap.length - 1) return false;
        return speedPoint.y !== speedMap[i + 1].y;
      });
      return isVariableSpeed;
    }

    function loadMarkers() {
      const markersUploadDiv = document.getElementById('markers-upload-div');

      if (markersUploadDiv) {
        $BHXf$export$deleteElement(markersUploadDiv);
      } else {
        const markersUploadDiv = document.createElement('div');
        markersUploadDiv.setAttribute('id', 'markers-upload-div');
        markersUploadDiv.innerHTML = $Xu5K$export$default`
          <fieldset>
            <legend>Upload a markers .json file.</legend>
            <input type="file" id="markers-json-input" />
            <input type="button" id="upload-markers-json" value="Load" />
          </fieldset>
        `;
        updateSettingsEditorHook();
        settingsEditorHook.insertAdjacentElement('beforebegin', markersUploadDiv);
        const fileUploadButton = document.getElementById('upload-markers-json');
        fileUploadButton.onclick = loadMarkersJson;
      }
    }

    function loadMarkersJson() {
      const input = document.getElementById('markers-json-input');
      console.log(input.files);
      const file = input.files[0];
      const fr = new FileReader();
      fr.onload = receivedJson;
      fr.readAsText(file);
      const markersUploadDiv = document.getElementById('markers-upload-div');
      $BHXf$export$deleteElement(markersUploadDiv);
    }

    function receivedJson(e) {
      const lines = e.target.result;
      const markersJson = JSON.parse(lines);
      console.log(markersJson);
      flashMessage('Loading markers...', 'green');

      if (markersJson) {
        // move markers field to marker Pairs for backwards compat)
        if (markersJson.markers && !markersJson.markerPairs) {
          markersJson.markerPairs = markersJson.markers;
          delete markersJson.markers;
        }

        if (!markersJson.markerPairs) {
          flashMessage('Could not find markers or markerPairs field. Could not load marker data.', 'red');
        } // copy markersJson to settings object less markerPairs field


        const _markerPairs = markersJson.markerPairs,
              loadedSettings = $XbmT$var$__rest(markersJson, ["markerPairs"]);
        settings = Object.assign(Object.assign({}, settings), loadedSettings);
        markersJson.markerPairs.forEach(markerPair => {
          const startMarkerConfig = {
            time: markerPair.start,
            type: 'start'
          };
          const endMarkerConfig = {
            time: markerPair.end,
            type: 'end',
            crop: markerPair.crop,
            speed: markerPair.speed,
            speedMap: markerPair.speedMap,
            speedMapLoop: markerPair.speedMapLoop,
            overrides: markerPair.overrides
          };
          addMarker(startMarkerConfig);
          addMarker(endMarkerConfig);
        });
      }
    } // set width and height attributes for browsers not supporting svg 2


    const marker_attrs = {
      class: 'marker',
      width: '1px',
      height: '14px',
      'shape-rendering': 'crispEdges'
    };

    function addMarker() {
      let markerConfig = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      const roughCurrentTime = markerConfig.time || $XbmT$export$player.getCurrentTime();
      const currentFrameTime = getCurrentFrameTime(roughCurrentTime);
      const progressPos = currentFrameTime / playerInfo.duration * 100;

      if (!start && currentFrameTime <= startTime) {
        flashMessage('Cannot add end marker before start marker.', 'red');
        return;
      }

      const marker = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
      markersSvg.appendChild(marker);
      $BHXf$export$setAttributes(marker, marker_attrs);
      marker.setAttribute('x', `${progressPos}%`);
      const rectIdx = markerPairs.length + 1;
      marker.setAttribute('idx', rectIdx.toString());

      if (start === true) {
        marker.classList.add('start-marker');
        marker.setAttribute('type', 'start');
        marker.setAttribute('z-index', '1');
        startTime = currentFrameTime;
      } else {
        marker.addEventListener('mouseover', toggleMarkerPairEditorHandler, false);
        marker.classList.add('end-marker');
        marker.setAttribute('type', 'end');
        marker.setAttribute('z-index', '2');
        const startProgressPos = startTime / playerInfo.duration * 100;

        const _addMarkerPairNumberi = addMarkerPairNumberings(rectIdx, startProgressPos, progressPos),
              _addMarkerPairNumberi2 = $XbmT$var$_slicedToArray(_addMarkerPairNumberi, 2),
              startNumbering = _addMarkerPairNumberi2[0],
              endNumbering = _addMarkerPairNumberi2[1];

        updateMarkerPairsArray(currentFrameTime, Object.assign(Object.assign({}, markerConfig), {
          startNumbering,
          endNumbering
        }));
        updateMarkerPairEditor();
      }

      start = !start;
      console.log(markerPairs);
    }

    function getFPS() {
      let defaultFPS = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 60;

      try {
        return parseFloat($XbmT$export$player.getStatsForNerds().resolution.match(/@(\d+)/)[1]);
      } catch (e) {
        console.log('Could not detect fps', e);
        return defaultFPS; // by default parameter value assume high fps to avoid skipping frames
      }
    }

    function getCurrentFrameTime(roughCurrentTime) {
      let currentFrameTime;
      let fps = getFPS();
      fps ? currentFrameTime = Math.floor(roughCurrentTime * fps) / fps : currentFrameTime = roughCurrentTime;
      return currentFrameTime;
    }

    function updateMarkerPairsArray(currentTime, markerPairConfig) {
      const speed = markerPairConfig.speed || settings.newMarkerSpeed;
      const newMarkerPair = {
        start: startTime,
        end: currentTime,
        crop: markerPairConfig.crop || settings.newMarkerCrop,
        speed: speed,
        outputDuration: markerPairConfig.outputDuration || currentTime - startTime,
        overrides: markerPairConfig.overrides || {},
        speedMapLoop: markerPairConfig.speedMapLoop || {
          enabled: true
        },
        speedMap: markerPairConfig.speedMap || [{
          x: startTime,
          y: speed
        }, {
          x: currentTime,
          y: speed
        }],
        startNumbering: markerPairConfig.startNumbering,
        endNumbering: markerPairConfig.endNumbering,
        moveHistory: {
          undos: [],
          redos: []
        }
      };
      markerPairs.push(newMarkerPair);
    }

    function updateMarkerPairEditor() {
      if (isMarkerPairSettingsEditorOpen) {
        const markerPairCountLabel = document.getElementById('marker-pair-count-label');

        if (markerPairCountLabel) {
          markerPairCountLabel.textContent = markerPairs.length.toString();
          markerPairNumberInput.setAttribute('max', markerPairs.length.toString());
        }
      }
    }

    function addMarkerPairNumberings(idx, startProgressPos, endProgressPos) {
      const startNumbering = $BHXf$export$htmlToSVGElement(`\
        <text class="markerNumbering startMarkerNumbering" idx="${idx}"\
        x="${startProgressPos}%" y="11.5px"
        text-anchor="middle">\
        ${idx}\
        </text>\
        `);
      const endNumbering = $BHXf$export$htmlToSVGElement(`\
        <text class="markerNumbering endMarkerNumbering" idx="${idx}"\
          x="${endProgressPos}%" y="11.5px"
          text-anchor="middle"
        >\
        ${idx}\
        </text>\
        `);
      const startNumberingText = startMarkerNumberings.appendChild(startNumbering);
      const endNumberingText = endMarkerNumberings.appendChild(endNumbering);
      return [startNumberingText, endNumberingText];
    }

    function undoMarker() {
      const targetMarker = markersSvg.lastElementChild;
      if (!targetMarker) return;
      const targetMarkerType = targetMarker.getAttribute('type'); // toggle off marker pair editor before undoing a selected marker pair

      if (targetMarkerType === 'end' && prevSelectedMarkerPairIndex >= markerPairs.length - 1) {
        if (isMarkerPairSettingsEditorOpen && !wasGlobalSettingsEditorOpen) {
          toggleOffMarkerPairEditor(true);
        } else {
          hideSelectedMarkerPairOverlay(true);
        }

        clearPrevSelectedMarkerPairReferences();
      }

      $BHXf$export$deleteElement(targetMarker);

      if (targetMarkerType === 'end') {
        const markerPair = markerPairs[markerPairs.length - 1];
        $BHXf$export$deleteElement(markerPair.startNumbering);
        $BHXf$export$deleteElement(markerPair.endNumbering);
        startTime = markerPair.start;
        markerPairsHistory.push(markerPairs.pop());
        console.log(markerPairs);
        updateMarkerPairEditor();
      }

      start = !start;
    }

    function redoMarker() {
      if (markerPairsHistory.length > 0) {
        const markerPairToRestore = markerPairsHistory[markerPairsHistory.length - 1];

        if (start) {
          addMarker({
            time: markerPairToRestore.start
          });
        } else {
          markerPairsHistory.pop();
          addMarker(Object.assign(Object.assign({}, markerPairToRestore), {
            time: markerPairToRestore.end
          }));
        }
      }
    }

    function cyclePlayerSpeedDown() {
      let newSpeed = $XbmT$export$player.getPlaybackRate() - 0.25;
      newSpeed = newSpeed <= 0 ? 1 : newSpeed;
      $XbmT$export$player.setPlaybackRate(newSpeed);
      flashMessage(`Video playback speed set to ${newSpeed}`, 'green');
    }

    function toggleGlobalSettingsEditor() {
      if (isMarkerPairSettingsEditorOpen && !wasGlobalSettingsEditorOpen) {
        toggleOffMarkerPairEditor();
      }

      if (wasGlobalSettingsEditorOpen) {
        toggleOffGlobalSettingsEditor();
      } else {
        createGlobalSettingsEditor();
      }
    }

    function toggleOffGlobalSettingsEditor() {
      deleteSettingsEditor();
      hideCropOverlay();
      hideSpeedChart();
    }

    let cropInput;
    let cropAspectRatioSpan;

    function createGlobalSettingsEditor() {
      createCropOverlay(settings.newMarkerCrop);
      const globalSettingsEditorDiv = document.createElement('div');
      const cropInputValidation = `\\d+:\\d+:(\\d+|iw):(\\d+|ih)`;

      const _getCropComponents5 = getCropComponents(settings.newMarkerCrop),
            _getCropComponents6 = $XbmT$var$_slicedToArray(_getCropComponents5, 4),
            x = _getCropComponents6[0],
            y = _getCropComponents6[1],
            w = _getCropComponents6[2],
            h = _getCropComponents6[3];

      const cropAspectRatio = (w / h).toFixed(13);
      const csvRange = `(\\d{1,2})([,-]\\d{1,2})*`;
      const mergeListInputValidation = `(${csvRange})+(;${csvRange})*`;
      const gte100 = `([1-9]\\d{3}|[1-9]\\d{2})`;
      const cropResInputValidation = `${gte100}x${gte100}`;
      const resList = playerInfo.isVerticalVideo ? `<option value="1080x1920"><option value="2160x3840">` : `<option value="1920x1080"><option value="3840x2160">`;
      const denoise = settings.denoise;
      const denoiseDesc = denoise ? denoise.desc : null;
      const vidstab = settings.videoStabilization;
      const vidstabDesc = vidstab ? vidstab.desc : null;
      const vidstabDynamicZoomEnabled = settings.videoStabilizationDynamicZoom;
      const markerPairMergelistDurations = getMarkerPairMergeListDurations();
      const globalEncodeSettingsEditorDisplay = isExtraSettingsEditorEnabled ? 'block' : 'none';
      globalSettingsEditorDiv.setAttribute('id', 'settings-editor-div');
      globalSettingsEditorDiv.innerHTML = `
    <fieldset id="new-marker-defaults-inputs" 
      class="settings-editor-panel global-settings-editor global-settings-editor-highlighted-div">
      <legend class="global-settings-editor-highlighted-label">New Marker Settings</legend>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.speedTooltip}">
        <span>Speed</span>
        <input id="speed-input" type="number" placeholder="speed" value="${settings.newMarkerSpeed}" step="0.05" min="0.05" max="2" style="min-width:4em">
      </div>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.cropTooltip}">
        <span>Crop</span>
        <input id="crop-input" value="${settings.newMarkerCrop}" pattern="${cropInputValidation}" style="min-width:10em" required>
      </div>
      <div class="settings-editor-input-div  settings-info-display">
        <span>Crop Aspect Ratio</span>
        <span id="crop-aspect-ratio">${cropAspectRatio}</span>
      </div>
    </fieldset>
    <fieldset id="global-marker-settings" 
    class="settings-editor-panel global-settings-editor global-settings-editor-highlighted-div">
      <legend class="global-settings-editor-highlighted-label settings-editor-panel-label">Global Settings</legend>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.titleSuffixTooltip}">
        <span>Title Suffix</span>
        <input id="title-suffix-input" value="${settings.titleSuffix}" style="background-color:lightgreen;min-width:20em;text-align:right" required>
      </div>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.cropResolutionTooltip}">
        <span>Crop Resolution</span>
        <input id="crop-res-input" list="resolutions" pattern="${cropResInputValidation}" value="${settings.cropRes}" style="min-width:7em" required>
        <datalist id="resolutions" autocomplete="off">${resList}</datalist>
      </div>
      <div id="global-settings-rotate" class="settings-editor-input-div" title="${$zc7V$export$Tooltips.rotateTooltip}">
        <span style="display:inline">Rotate: </span>
        <input id="rotate-0" type="radio" name="rotate" value="0" ${settings.rotate == null || settings.rotate === '0' ? 'checked' : ''}></input>
        <label for="rotate-0">0&#x00B0; </label>
        <input id="rotate-90-clock" type="radio" value="clock" name="rotate" ${settings.rotate === 'clock' ? 'checked' : ''}></input>
        <label for="rotate-90-clock">90&#x00B0; &#x27F3;</label>
        <input id="rotate-90-counterclock" type="radio" value="cclock" name="rotate" ${settings.rotate === 'cclock' ? 'checked' : ''}></input>
        <label for="rotate-90-counterclock">90&#x00B0; &#x27F2;</label>
      </div>
      <div id="merge-list-div" class="settings-editor-input-div" title="${$zc7V$export$Tooltips.mergeListTooltip}">
          <span style="display:inline">Merge List: </span>
          <input id="merge-list-input" pattern="${mergeListInputValidation}" value="${settings.markerPairMergeList != null ? settings.markerPairMergeList : ''}" placeholder="None" style="min-width:15em">
      </div>
      <div class="settings-editor-input-div">
        <span style="display:inline">Merge Durations: </span>
        <span id="merge-list-durations" style="display:inline">${markerPairMergelistDurations}</span>
      </div>
    </fieldset>
    <fieldset id="global-encode-settings" 
      class="settings-editor-panel global-settings-editor global-settings-editor-highlighted-div" style="display:${globalEncodeSettingsEditorDisplay}">
      <legend class="global-settings-editor-highlighted-label">Encode Settings</legend>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.audioTooltip}">
        <span>Audio</span>
        <select id="audio-input"> 
          <option ${settings.audio ? 'selected' : ''}>Enabled</option>
          <option ${settings.audio === false ? 'selected' : ''}>Disabled</option>
          <option value="Default" ${settings.audio == null ? 'selected' : ''}>Inherit (Disabled)</option>
        </select>
      </div>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.encodeSpeedTooltip}">
        <span>Encode Speed (0-5)</span>
        <input id="encode-speed-input" type="number" min="0" max="5" step="1" value="${settings.encodeSpeed != null ? settings.encodeSpeed : ''}" placeholder="Auto" style="min-width:4em"></input>
      </div>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.CRFTooltip}">
        <span>CRF (0-63)</span>
        <input id="crf-input" type="number" min="0" max="63" step="1" value="${settings.crf != null ? settings.crf : ''}" placeholder="Auto" style="min-width:4em"></input>
      </div>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.targetBitrateTooltip}">
        <span>Target Bitrate (kb/s)</span>
        <input id="target-max-bitrate-input" type="number" min="0" max="1e5"step="100" value="${settings.targetMaxBitrate != null ? settings.targetMaxBitrate : ''}" placeholder="Auto" "style="min-width:4em"></input>
      </div>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.twoPassTooltip}">
        <span>Two-Pass</span>
        <select id="two-pass-input"> 
          <option ${settings.twoPass ? 'selected' : ''}>Enabled</option>
          <option ${settings.twoPass === false ? 'selected' : ''}>Disabled</option>
          <option value="Default" ${settings.twoPass == null ? 'selected' : ''}>Inherit (Disabled)</option>
        </select>
      </div>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.gammaTooltip}">
        <span>Gamma (0-4)</span>
        <input id="gamma-input" type="number" min="0.01" max="4.00" step="0.01" value="${settings.gamma != null ? settings.gamma : ''}" placeholder="1" style="min-width:4em"></input>
      </div>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.expandColorRangeTooltip}">
        <span>Expand Colors</span>
        <select id="expand-color-range-input"> 
          <option ${settings.expandColorRange ? 'selected' : ''}>Enabled</option>
          <option ${settings.expandColorRange === false ? 'selected' : ''}>Disabled</option>
          <option value="Default" ${settings.expandColorRange == null ? 'selected' : ''}>Inherit (Disabled)</option>
        </select>
      </div>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.denoiseTooltip}">
        <span>Denoise</span>
        <select id="denoise-input">
          <option ${denoiseDesc === 'Very Strong' ? 'selected' : ''}>Very Strong</option>
          <option ${denoiseDesc === 'Strong' ? 'selected' : ''}>Strong</option>
          <option ${denoiseDesc === 'Medium' ? 'selected' : ''}>Medium</option>
          <option ${denoiseDesc === 'Weak' ? 'selected' : ''}>Weak</option>
          <option ${denoiseDesc === 'Very Weak' ? 'selected' : ''}>Very Weak</option>
          <option value="Inherit" ${denoiseDesc == null ? 'selected' : ''}>Inherit (Disabled)</option>
        </select>
      </div>
      <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.speedMapTooltip}">
        <span>Speed Maps</span>
          <select id="enable-speed-maps-input">
            <option ${settings.enableSpeedMaps ? 'selected' : ''}>Enabled</option>
            <option ${settings.enableSpeedMaps === false ? 'selected' : ''}>Disabled</option>
            <option value="Default" ${settings.enableSpeedMaps == null ? 'selected' : ''}>Inherit (Enabled)</option>
          </select>
      </div>
      <div class="settings-editor-input-div multi-input-div" title="${$zc7V$export$Tooltips.vidstabTooltip}">
        <div>
          <span>Stabilization</span>
          <select id="video-stabilization-input">
            <option ${vidstabDesc === 'Strongest' ? 'selected' : ''}>Strongest</option>
            <option ${vidstabDesc === 'Very Strong' ? 'selected' : ''}>Very Strong</option>
            <option ${vidstabDesc === 'Strong' ? 'selected' : ''}>Strong</option>
            <option ${vidstabDesc === 'Medium' ? 'selected' : ''}>Medium</option>
            <option ${vidstabDesc === 'Weak' ? 'selected' : ''}>Weak</option>
            <option ${vidstabDesc === 'Very Weak' ? 'selected' : ''}>Very Weak</option>
            <option value="Inherit" ${vidstabDesc == null ? 'selected' : ''}>Inherit (Disabled)</option>
          </select>
        </div>
        <div title="${$zc7V$export$Tooltips.dynamicZoomTooltip}">
          <span>Dynamic Zoom</span>
          <select id="video-stabilization-dynamic-zoom-input"> 
            <option ${vidstabDynamicZoomEnabled ? 'selected' : ''}>Enabled</option>
            <option ${vidstabDynamicZoomEnabled === false ? 'selected' : ''}>Disabled</option>
            <option value="Default" ${vidstabDynamicZoomEnabled == null ? 'selected' : ''}>Inherit (Disabled)</option>
          </select>
        </div>
      </div>
      <div class="settings-editor-input-div multi-input-div" title="${$zc7V$export$Tooltips.loopTooltip}">
        <div>
          <span>Loop</span>
          <select id="loop-input">
            <option ${settings.loop === 'fwrev' ? 'selected' : ''}>fwrev</option>
            <option ${settings.loop === 'fade' ? 'selected' : ''}>fade</option>
            <option ${settings.loop === 'none' ? 'selected' : ''}>none</option>
            <option value="Default" ${settings.loop == null ? 'selected' : ''}>Inherit (none)</option>
          </select>
        </div>
        <div title="${$zc7V$export$Tooltips.fadeDurationTooltip}">
          <span>Fade Duration</span>
          <input id="fade-duration-input" type="number" min="0.1" step="0.1" value="${settings.fadeDuration != null ? settings.fadeDuration : ''}" placeholder="0.5" style="width:7em"></input>
        </div>
      </div>
    </fieldset>
    `;
      updateSettingsEditorHook();
      settingsEditorHook.insertAdjacentElement('beforebegin', globalSettingsEditorDiv);
      const settingsInputsConfigs = [['crop-res-input', 'cropRes', 'string']];
      const settingsInputsConfigsHighlightable = [['crop-input', 'newMarkerCrop', 'string'], ['speed-input', 'newMarkerSpeed', 'number'], ['title-suffix-input', 'titleSuffix', 'string'], ['merge-list-input', 'markerPairMergeList', 'string'], ['gamma-input', 'gamma', 'number'], ['encode-speed-input', 'encodeSpeed', 'number'], ['crf-input', 'crf', 'number'], ['target-max-bitrate-input', 'targetMaxBitrate', 'number'], ['rotate-0', 'rotate', 'string'], ['rotate-90-clock', 'rotate', 'string'], ['rotate-90-counterclock', 'rotate', 'string'], ['two-pass-input', 'twoPass', 'ternary'], ['audio-input', 'audio', 'ternary'], ['expand-color-range-input', 'expandColorRange', 'ternary'], ['denoise-input', 'denoise', 'preset'], ['video-stabilization-input', 'videoStabilization', 'preset'], ['video-stabilization-dynamic-zoom-input', 'videoStabilizationDynamicZoom', 'ternary'], ['enable-speed-maps-input', 'enableSpeedMaps', 'ternary'], ['loop-input', 'loop', 'inheritableString'], ['fade-duration-input', 'fadeDuration', 'number']];
      addSettingsInputListeners(settingsInputsConfigs, settings, false);
      addSettingsInputListeners(settingsInputsConfigsHighlightable, settings, true);
      cropInput = document.getElementById('crop-input');
      cropAspectRatioSpan = document.getElementById('crop-aspect-ratio');
      wasGlobalSettingsEditorOpen = true;
      isMarkerPairSettingsEditorOpen = true;
      addMarkerPairMergeListDurationsListener();
      addCropInputHotkeys();
      highlightModifiedSettings(settingsInputsConfigsHighlightable, settings);
    }

    function addSettingsInputListeners(inputs, target) {
      let highlightable = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
      inputs.forEach(input => {
        const id = input[0];
        const targetProperty = input[1];
        const valueType = input[2] || 'string';
        const inputElem = document.getElementById(id);
        inputElem.addEventListener('focus', () => toggleKeys = false, false);
        inputElem.addEventListener('blur', () => toggleKeys = true, false);
        inputElem.addEventListener('change', e => updateSettingsValue(e, id, target, targetProperty, valueType, highlightable), false);
      });
    }

    const presetsMap = {
      videoStabilization: {
        Disabled: {
          desc: 'Disabled',
          enabled: false
        },
        'Very Weak': {
          desc: 'Very Weak',
          enabled: true,
          shakiness: 2,
          smoothing: 2,
          zoomspeed: 0.05
        },
        Weak: {
          desc: 'Weak',
          enabled: true,
          shakiness: 4,
          smoothing: 4,
          zoomspeed: 0.1
        },
        Medium: {
          desc: 'Medium',
          enabled: true,
          shakiness: 6,
          smoothing: 6,
          zoomspeed: 0.2
        },
        Strong: {
          desc: 'Strong',
          enabled: true,
          shakiness: 8,
          smoothing: 10,
          zoomspeed: 0.3
        },
        'Very Strong': {
          desc: 'Very Strong',
          enabled: true,
          shakiness: 10,
          smoothing: 16,
          zoomspeed: 0.4
        },
        Strongest: {
          desc: 'Strongest',
          enabled: true,
          shakiness: 10,
          smoothing: 0,
          zoomspeed: 0.5
        }
      },
      denoise: {
        Disabled: {
          enabled: false,
          desc: 'Disabled'
        },
        'Very Weak': {
          enabled: true,
          shakiness: 1,
          desc: 'Very Weak'
        },
        Weak: {
          enabled: true,
          lumaSpatial: 2,
          desc: 'Weak'
        },
        Medium: {
          enabled: true,
          lumaSpatial: 4,
          desc: 'Medium'
        },
        Strong: {
          enabled: true,
          lumaSpatial: 6,
          desc: 'Strong'
        },
        'Very Strong': {
          enabled: true,
          lumaSpatial: 8,
          desc: 'Very Strong'
        }
      }
    };

    function updateSettingsValue(e, id, target, targetProperty, valueType, highlightable) {
      if (e.target.reportValidity()) {
        let newValue = e.target.value;

        if (newValue != null) {
          if (targetProperty !== 'titleSuffix' && targetProperty !== 'markerPairMergeList' && newValue === '') {
            delete target[targetProperty];
            newValue = undefined;
          } else if (valueType === 'number') {
            newValue = parseFloat(newValue);
          } else if (valueType === 'boolean') {
            newValue = e.target.checked;
          } else if (valueType === 'ternary' || valueType === 'inheritableString') {
            if (newValue === 'Default' || newValue === 'Inherit') {
              delete target[targetProperty];
              newValue = undefined;
            } else if (newValue === 'Enabled') {
              newValue = true;
            } else if (newValue === 'Disabled') {
              newValue = false;
            } else {
              newValue = newValue.toLowerCase();
            }
          } else if (valueType === 'preset') {
            if (newValue === 'Inherit') {
              delete target[targetProperty];
              newValue = undefined;
            } else {
              newValue = presetsMap[targetProperty][newValue];
            }
          }
        }

        target[targetProperty] = newValue;

        if (targetProperty === 'newMarkerCrop') {
          createCropOverlay(target.newMarkerCrop);
        }

        if (targetProperty === 'cropRes') {
          const prevWidth = target.cropResWidth;
          const prevHeight = target.cropResHeight;

          const _target$cropRes$split = target.cropRes.split('x').map(str => parseInt(str), 10),
                _target$cropRes$split2 = $XbmT$var$_slicedToArray(_target$cropRes$split, 2),
                newWidth = _target$cropRes$split2[0],
                newHeight = _target$cropRes$split2[1];

          const cropMultipleX = newWidth / prevWidth;
          const cropMultipleY = newHeight / prevHeight;
          target.cropResWidth = newWidth;
          target.cropResHeight = newHeight;
          multiplyAllCrops(cropMultipleX, cropMultipleY);
        }

        if (targetProperty === 'crop') {
          const _getCropComponents7 = getCropComponents(newValue),
                _getCropComponents8 = $XbmT$var$_slicedToArray(_getCropComponents7, 4),
                x = _getCropComponents8[0],
                y = _getCropComponents8[1],
                w = _getCropComponents8[2],
                h = _getCropComponents8[3];

          setCropOverlayDimensions(x, y, w, h);
          const cropAspectRatio = (w / h).toFixed(13);
          cropAspectRatioSpan && (cropAspectRatioSpan.textContent = cropAspectRatio);
        }

        if (targetProperty === 'speed') {
          const speedMap = target.speedMap;

          if (speedMap.length === 2 && speedMap[0].y === speedMap[1].y) {
            target.speedMap[1].y = newValue;
          }

          target.speedMap[0].y = newValue;
          speedChart && speedChart.update();
          updateMarkerPairDuration(target);
        }
      }

      if (highlightable) highlightModifiedSettings([[id, targetProperty, valueType]], target);
    }

    function multiplyAllCrops(cropMultipleX, cropMultipleY) {
      const cropString = settings.newMarkerCrop;
      const multipliedCropString = multiplyCropString(cropMultipleX, cropMultipleY, cropString);
      settings.newMarkerCrop = multipliedCropString;
      cropInput.value = multipliedCropString;

      if (markerPairs) {
        markerPairs.forEach(markerPair => {
          const multipliedCropString = multiplyCropString(cropMultipleX, cropMultipleY, markerPair.crop);
          markerPair.crop = multipliedCropString;
        });
      }
    }

    function multiplyCropString(cropMultipleX, cropMultipleY, cropString) {
      let _cropString$split = cropString.split(':'),
          _cropString$split2 = $XbmT$var$_slicedToArray(_cropString$split, 4),
          x = _cropString$split2[0],
          y = _cropString$split2[1],
          width = _cropString$split2[2],
          height = _cropString$split2[3];

      x = Math.round(x * cropMultipleX);
      y = Math.round(y * cropMultipleY);
      width = width !== 'iw' ? Math.round(width * cropMultipleX) : width;
      height = height !== 'ih' ? Math.round(height * cropMultipleY) : height;
      const multipliedCropString = [x, y, width, height].join(':');
      return multipliedCropString;
    }

    function getMarkerPairMergeListDurations() {
      let markerPairMergeList = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : settings.markerPairMergeList;
      const durations = [];

      for (let merge of markerPairMergeList.split(';')) {
        let duration = 0;

        for (let mergeRange of merge.split(',')) {
          if (mergeRange.includes('-')) {
            let _mergeRange$split$map = mergeRange.split('-').map(str => parseInt(str, 10) - 1),
                _mergeRange$split$map2 = $XbmT$var$_slicedToArray(_mergeRange$split$map, 2),
                mergeRangeStart = _mergeRange$split$map2[0],
                mergeRangeEnd = _mergeRange$split$map2[1];

            if (mergeRangeStart > mergeRangeEnd) {
              var _ref = [mergeRangeEnd, mergeRangeStart];
              mergeRangeStart = _ref[0];
              mergeRangeEnd = _ref[1];
            }

            for (let idx = mergeRangeStart; idx <= mergeRangeEnd; idx++) {
              if (!isNaN(idx) && idx >= 0 && idx < markerPairs.length) {
                const marker = markerPairs[idx];
                duration += (marker.end - marker.start) / marker.speed;
              }
            }
          } else {
            const idx = parseInt(mergeRange, 10) - 1;

            if (!isNaN(idx) && idx >= 0 && idx < markerPairs.length) {
              const marker = markerPairs[idx];
              duration += (marker.end - marker.start) / marker.speed;
            }
          }
        }

        durations.push(duration);
      }

      const markerPairMergelistDurations = durations.map($BHXf$export$toHHMMSSTrimmed).join(' ; ');
      return markerPairMergelistDurations;
    }

    function addCropInputHotkeys() {
      cropInput.addEventListener('keydown', ke => {
        if (ke.code === 'ArrowUp' || ke.code === 'ArrowDown') {
          let cropString = cropInput.value;
          let cropStringArray = cropString.split(':');
          let cropArray = getCropComponents(cropString); // let [x, y, w, ] = cropArray;

          const cropStringCursorPos = ke.target.selectionStart;
          let cropComponentCursorPos = cropStringCursorPos;
          let cropTarget = 0;

          while (cropComponentCursorPos - (cropStringArray[cropTarget].length + 1) >= 0) {
            cropComponentCursorPos -= cropStringArray[cropTarget].length + 1;
            cropTarget++;
          }

          if (cropTarget >= 0 && cropTarget <= cropArray.length - 1 && typeof cropArray[cropTarget] === 'number') {
            let changeAmount;

            if (!ke.altKey && !ke.shiftKey) {
              changeAmount = 10;
            } else if (ke.altKey && !ke.shiftKey) {
              changeAmount = 1;
            } else if (!ke.altKey && ke.shiftKey) {
              changeAmount = 50;
            } else if (ke.altKey && ke.shiftKey) {
              changeAmount = 100;
            }

            if (ke.code === 'ArrowUp') {
              cropArray[cropTarget] += changeAmount;
            } else if (ke.code === 'ArrowDown') {
              cropArray[cropTarget] -= changeAmount;
            }

            ke.preventDefault();
            ke.stopImmediatePropagation();
            cropArray = clampCropArray(cropArray, cropTarget);
            const updatedCropString = cropArray.join(':');
            cropInput.value = updatedCropString;
            let newCursorPos = cropStringCursorPos - cropComponentCursorPos;

            if (cropTarget === 3 && cropStringArray[3] === 'ih') {
              const cropStringLengthDelta = updatedCropString.length - cropString.length;
              const cursorPosAdjustment = cropStringLengthDelta - cropComponentCursorPos;
              newCursorPos += cursorPosAdjustment;
            }

            cropInput.selectionStart = newCursorPos;
            cropInput.selectionEnd = newCursorPos;
            cropInput.dispatchEvent(new Event('change'));
          }
        }
      });
    }

    function clampCropArray(cropArray, target) {
      let _cropArray = $XbmT$var$_slicedToArray(cropArray, 4),
          x = _cropArray[0],
          y = _cropArray[1],
          w = _cropArray[2],
          h = _cropArray[3];

      switch (target) {
        case 'x':
        case 0:
          x = $BHXf$export$clampNumber(x, 0, settings.cropResWidth - w);
          break;

        case 'y':
        case 1:
          y = $BHXf$export$clampNumber(y, 0, settings.cropResHeight - h);
          break;

        case 'w':
        case 2:
          w = $BHXf$export$clampNumber(w, 0, settings.cropResWidth - x);
          break;

        case 'h':
        case 3:
          h = $BHXf$export$clampNumber(h, 0, settings.cropResHeight - y);
          break;
      }

      return [x, y, w, h];
    }

    function addMarkerPairMergeListDurationsListener() {
      const markerPairMergeListInput = document.getElementById('merge-list-input');
      const markerPairMergeListDurationsSpan = document.getElementById('merge-list-durations');
      markerPairMergeListInput.addEventListener('change', () => {
        const markerPairMergelistDurations = getMarkerPairMergeListDurations();
        markerPairMergeListDurationsSpan.textContent = markerPairMergelistDurations;
      });
    }

    let shortcutsTableToggleButton;

    function injectToggleShortcutsTableButton() {
      const ytpRightControls = document.getElementsByClassName('ytp-right-controls')[0];
      shortcutsTableToggleButton = $BHXf$export$htmlToElement($XbmT$var$shortcutsTableToggleButtonHTML);
      shortcutsTableToggleButton.onclick = toggleShortcutsTable;
      ytpRightControls.insertAdjacentElement('afterbegin', shortcutsTableToggleButton);
    }

    function showShortcutsTableToggleButton() {
      if (shortcutsTableToggleButton) {
        shortcutsTableToggleButton.style.display = 'inline-block';
      }
    }

    function hideShortcutsTableToggleButton() {
      if (shortcutsTableToggleButton) {
        shortcutsTableToggleButton.style.display = 'none';
      }
    }

    let shortcutsTableContainer;

    function toggleShortcutsTable() {
      if (!shortcutsTableContainer) {
        injectCSS($XbmT$var$shortcutsTableStyle, 'shortcutsTableStyle');
        shortcutsTableContainer = document.createElement('div');
        shortcutsTableContainer.setAttribute('id', 'shortcutsTableContainer');
        shortcutsTableContainer.innerHTML = $XbmT$var$shortcutsTable;
        flashMessageHook.insertAdjacentElement('afterend', shortcutsTableContainer);
      } else if (shortcutsTableContainer.style.display !== 'none') {
        shortcutsTableContainer.style.display = 'none';
      } else {
        shortcutsTableContainer.style.display = 'block';
      }
    }

    const frameCaptureViewerHeadHTML = `\
      <title>yt_clipper Frame Capture Viewer</title>
      <style>
        body {
          margin: 0px;
          text-align: center;
        }
        #frames-div {
          font-family: Helvetica;
          background-color: rgb(160,50,20);
          margin: 0 auto;
          padding: 2px;
          width: 99%;
          text-align: center;
        }
        .frame-div {
          margin: 2px;
          padding: 2px;
          border: 2px black solid;
          font-weight: bold;
          color: black;
          text-align: center;
        }
        figcaption {
          display: inline-block;
          margin: 2px;
        }
        button {
          display: inline-block;
          font-weight: bold;
          margin-bottom: 2px;
          cursor: pointer;
          border: 2px solid black;
          border-radius: 4px;
        }
        button.download {
          background-color: rgb(66, 134, 244);
        }
        button.delete {
          background-color: red;
        }
        button:hover {
          box-shadow: 2px 4px 4px 0 rgba(0,0,0,0.2);
        }
        canvas {
          display: block;
          margin: 0 auto;
          ${$XbmT$export$player.getVideoAspectRatio() > 1 ? 'width: 98%;' : 'height: 96vh;'}
        }
        @keyframes flash {
          0% {
            opacity: 1;
          }
          100% {
            opacity: 0.5;
          }
        }
        .flash-div {
          animation-name: flash;
          animation-duration: 0.5s;
          animation-fill-mode: forwards;
        }
        </style>
      `;
    const frameCaptureViewerBodyHTML = `\
        <div id="frames-div"><strong></strong></div>
        `;
    let frameCaptureViewer;
    let frameCaptureViewerDoc;

    async function captureFrame() {
      const currentTime = video.currentTime;

      for (let i = 0; i < video.buffered.length; i++) {
        console.log(video.buffered.start(i), video.buffered.end(i));

        if (video.buffered.start(i) <= currentTime && currentTime <= video.buffered.end(i)) {
          break;
        }

        if (i === video.buffered.length - 1) {
          flashMessage('Frame not captured. Video has not yet buffered the frame.', 'red');
          return;
        }
      }

      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      let resString;

      if (isMarkerPairSettingsEditorOpen && !wasGlobalSettingsEditorOpen) {
        const idx = parseInt(prevSelectedEndMarker.getAttribute('idx'), 10) - 1;
        const markerPair = markerPairs[idx];
        const cropMultipleX = video.videoWidth / settings.cropResWidth;
        const cropMultipleY = video.videoHeight / settings.cropResHeight;
        resString = multiplyCropString(cropMultipleX, cropMultipleY, markerPair.crop);

        const _getCropComponents9 = getCropComponents(resString),
              _getCropComponents10 = $XbmT$var$_slicedToArray(_getCropComponents9, 4),
              x = _getCropComponents10[0],
              y = _getCropComponents10[1],
              w = _getCropComponents10[2],
              h = _getCropComponents10[3];

        canvas.width = w;
        canvas.height = h;

        if (h > w) {
          canvas.style.height = '96vh';
          canvas.style.width = 'auto';
        }

        context.drawImage(video, x, y, w, h, 0, 0, w, h);
        resString = `x${x}y${y}w${w}h${h}`;
      } else {
        resString = `x0y0w${video.videoWidth}h${video.videoHeight}`;
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
      }

      if (!frameCaptureViewer || !frameCaptureViewerDoc || frameCaptureViewer.closed) {
        frameCaptureViewer = window.open('', 'window', `height=${window.innerHeight}, width=${window.innerWidth}`);

        frameCaptureViewer.downloadFrame = btn => {
          const frameCanvas = btn.parentElement.querySelector('canvas');
          frameCanvas.toBlob(blob => $oSxG$exports.saveAs(blob, frameCanvas.fileName));
        };

        frameCaptureViewer.deleteFrame = btn => {
          const frameDiv = btn.parentElement;
          frameDiv.setAttribute('class', 'frame-div flash-div');
          setTimeout(() => $BHXf$export$deleteElement(frameDiv), 300);
        };

        frameCaptureViewerDoc = frameCaptureViewer.document;
        frameCaptureViewerDoc.head.innerHTML = frameCaptureViewerHeadHTML;
        frameCaptureViewerDoc.body.innerHTML = frameCaptureViewerBodyHTML;
      }

      const frameDiv = document.createElement('div');
      frameDiv.setAttribute('class', 'frame-div');
      const frameCount = getFrameCount(currentTime);
      const frameFileName = `${settings.titleSuffix}-${resString}-@${currentTime}s(${$BHXf$export$toHHMMSSTrimmed(currentTime).replace(':', ';')})-f${frameCount.frameNumber}(${frameCount.totalFrames})`;
      frameDiv.innerHTML = `\
      <figcaption>Resolution: ${canvas.width}x${canvas.height} Name: ${frameFileName}</figcaption>
      <button class="download" onclick="downloadFrame(this)">Download Frame</button>
      <button class="delete" onclick="deleteFrame(this)">Delete Frame</button>
      `;
      canvas.fileName = `${frameFileName}.png`;
      const framesDiv = frameCaptureViewerDoc.getElementById('frames-div');
      frameDiv.appendChild(canvas);
      framesDiv.appendChild(frameDiv);
      flashMessage(`Captured frame: ${frameFileName}`, 'green');
    }

    function getFrameCount(seconds) {
      let fps = getFPS(null);
      let frameNumber;
      let totalFrames;

      if (fps) {
        frameNumber = Math.floor(seconds * fps);
        totalFrames = Math.floor(video.duration * fps);
      } else {
        frameNumber = 'Unknown';
        totalFrames = 'Unknown';
      }

      return {
        frameNumber,
        totalFrames
      };
    }

    function canvasBlobToPromise(canvas) {
      return new Promise(resolve => {
        canvas.toBlob(blob => resolve(blob));
      });
    }

    let isFrameCapturerZippingInProgress = false;

    function saveCapturedFrames() {
      if (isFrameCapturerZippingInProgress) {
        flashMessage('Frame Capturer zipping already in progress. Please wait before trying to zip again.', 'red');
        return;
      }

      if (!frameCaptureViewer || frameCaptureViewer.closed || !frameCaptureViewerDoc) {
        flashMessage('Frame capturer not open. Please capture a frame before zipping.', 'olive');
        return;
      }

      var $ZBU$$interop$default = $parcel$interopDefault($ZBU$exports);
      const zip = new $ZBU$$interop$default.d();
      const framesZip = zip.folder(settings.titleSuffix).folder('frames');
      const frames = frameCaptureViewerDoc.getElementsByTagName('canvas');

      if (frames.length === 0) {
        flashMessage('No frames to zip.', 'olive');
        return;
      }

      isFrameCapturerZippingInProgress = true;
      Array.from(frames).forEach(frame => {
        framesZip.file(frame.fileName, canvasBlobToPromise(frame), {
          binary: true
        });
      });
      const progressDiv = injectProgressBar('green');
      const progressSpan = progressDiv.firstElementChild;
      zip.generateAsync({
        type: 'blob'
      }, metadata => {
        const percent = metadata.percent.toFixed(2) + '%';
        progressSpan.textContent = `Frame Capturer Zipping Progress: ${percent}`;
      }).then(blob => {
        $oSxG$exports.saveAs(blob, `${settings.titleSuffix}-frames.zip`);
        progressDiv.dispatchEvent(new Event('done'));
        isFrameCapturerZippingInProgress = false;
      });
    }

    function injectProgressBar(color) {
      const progressDiv = document.createElement('div');
      progressDiv.setAttribute('class', 'msg-div');
      progressDiv.addEventListener('done', () => {
        progressDiv.setAttribute('class', 'msg-div flash-div');
        setTimeout(() => $BHXf$export$deleteElement(progressDiv), 2500);
      });
      progressDiv.innerHTML = `<span class="flash-msg" style="color:${color}"> Frame Capturer Zipping Progress: 0%</span>`;
      flashMessageHook.insertAdjacentElement('beforebegin', progressDiv);
      return progressDiv;
    }

    let cropSvg;
    let cropDim;
    let cropRect;
    let cropRectBorderBlack;
    let cropRectBorderWhite;

    function createCropOverlay(cropString) {
      deleteCropOverlay();
      const cropDiv = document.createElement('div');
      cropDiv.setAttribute('id', 'crop-div');
      cropDiv.innerHTML = `\
      <svg id="crop-svg">
        <defs>
          <mask id="cropMask">
            <rect x="0" y="0" width="100%" height="100%" fill="white"/>
            <rect id="cropRect" x="0" y="0" width="100%" height="100%" fill="black"/>
          </mask>
        </defs>
        <rect id="cropDim" mask="url(#cropMask)" x="0" y="0" width="100%" height="100%" fill="black" fill-opacity="${cropDimOpacity}"/>
        <rect id="cropRectBorderBlack" x="0" y="0" width="100%" height="100%" fill="none" stroke="black" shape-rendering="geometricPrecision" stroke-width="1px" stroke-opacity="0.9" />
        <rect id="cropRectBorderWhite" x="0" y="0" width="100%" height="100%" fill="none" stroke="white" shape-rendering="geometricPrecision" stroke-width="1px" stroke-dasharray="5 5" stroke-opacity="0.9"/>
      </svg>`;
      resizeCropOverlay(cropDiv);
      overlayHook.insertAdjacentElement('afterend', cropDiv);
      window.addEventListener('resize', () => resizeCropOverlay(cropDiv));
      cropSvg = cropDiv.firstElementChild;
      cropDim = document.getElementById('cropDim');
      cropRect = document.getElementById('cropRect');
      cropRectBorderBlack = document.getElementById('cropRectBorderBlack');
      cropRectBorderWhite = document.getElementById('cropRectBorderWhite');

      const _getCropComponents11 = getCropComponents(cropString),
            _getCropComponents12 = $XbmT$var$_slicedToArray(_getCropComponents11, 4),
            x = _getCropComponents12[0],
            y = _getCropComponents12[1],
            w = _getCropComponents12[2],
            h = _getCropComponents12[3];

      setCropOverlayDimensions(x, y, w, h);
      isCropOverlayVisible = true;
    }

    function resizeCropOverlay(cropDiv) {
      requestAnimationFrame(() => forceRerenderCrop(cropDiv));
    }

    function forceRerenderCrop(cropDiv) {
      cropDiv.setAttribute('style', video.getAttribute('style') + 'position:absolute');

      if (cropSvg) {
        cropSvg.setAttribute('width', '0');
      }
    }

    function setCropOverlayDimensions(x, y, w, h) {
      if (cropRect && cropRectBorderBlack && cropRectBorderWhite) {
        const cropRectAttrs = {
          x: `${x / settings.cropResWidth * 100}%`,
          y: `${y / settings.cropResHeight * 100}%`,
          width: `${w / settings.cropResWidth * 100}%`,
          height: `${h / settings.cropResHeight * 100}%`
        };
        $BHXf$export$setAttributes(cropRect, cropRectAttrs);
        $BHXf$export$setAttributes(cropRectBorderBlack, cropRectAttrs);
        $BHXf$export$setAttributes(cropRectBorderWhite, cropRectAttrs);
      }
    }

    let cropDimOpacity = 0.5;

    function cycleCropDimOpacity() {
      if (cropDim) {
        cropDimOpacity += 0.25;
        cropDimOpacity = cropDimOpacity > 1 ? 0 : cropDimOpacity;
        cropDim.setAttribute('fill-opacity', cropDimOpacity.toString());
      }
    }

    function showCropOverlay() {
      if (cropSvg) {
        cropSvg.style.display = 'block';
        isCropOverlayVisible = true;
      }
    }

    function hideCropOverlay() {
      if (isDrawingCrop) {
        finishDrawingCrop();
      }

      if (cropSvg) {
        cropSvg.style.display = 'none';
        isCropOverlayVisible = false;
      }
    }

    function deleteCropOverlay() {
      const cropDiv = document.getElementById('crop-div');
      $BHXf$export$deleteElement(cropDiv);
      isCropOverlayVisible = false;
    }

    let isDrawingCrop = false;
    let prevCropString = '0:0:iw:ih';
    let beginDrawHandler;

    function drawCropOverlay(verticalFill) {
      if (isDrawingCrop) {
        finishDrawingCrop(prevCropString);
      } else if (isSpeedChartVisible) {
        flashMessage('Please toggle off the time-variable speed chart before drawing crop', 'olive');
      } else if (isDraggingCrop) {
        flashMessage('Please finish dragging or resizing before drawing crop', 'olive');
      } else if (isMarkerPairSettingsEditorOpen && isCropOverlayVisible) {
        isDrawingCrop = true;

        if (!wasGlobalSettingsEditorOpen) {
          prevCropString = markerPairs[prevSelectedMarkerPairIndex].crop;
        } else {
          prevCropString = settings.newMarkerCrop;
        }

        window.removeEventListener('keydown', addCropOverlayHoverListener, true);
        window.removeEventListener('mousemove', cropOverlayHoverHandler, true);
        hidePlayerControls();
        video.style.removeProperty('cursor');
        playerInfo.container.style.cursor = 'crosshair';

        beginDrawHandler = e => beginDraw(e, verticalFill);

        playerInfo.container.addEventListener('pointerdown', beginDrawHandler, {
          once: true,
          capture: true
        });
        flashMessage('Begin drawing crop', 'green');
      } else {
        flashMessage('Please open the global settings or a marker pair editor before drawing crop', 'olive');
      }
    }

    function hidePlayerControls() {
      playerInfo.controls.style.display = 'none';
      playerInfo.gradientBottom.style.display = 'none';
    }

    function showPlayerControls() {
      playerInfo.controls.style.display = 'block';
      playerInfo.gradientBottom.style.display = 'block';
    }

    let dragCropPreviewHandler;

    function beginDraw(e, verticalFill) {
      if (e.button == 0 && !dragCropPreviewHandler) {
        e.preventDefault();
        video.setPointerCapture(e.pointerId);
        const videoRect = $XbmT$export$player.getVideoContentRect();
        const playerRect = $XbmT$export$player.getBoundingClientRect();
        const ix = e.pageX - videoRect.left - playerRect.left;
        let ixScaled = Math.round(ix / videoRect.width * settings.cropResWidth);
        let iyScaled = 0;

        if (!verticalFill) {
          const iy = e.pageY - videoRect.top - playerRect.top;
          iyScaled = Math.round(iy / videoRect.height * settings.cropResHeight);
        }

        ixScaled = $BHXf$export$clampNumber(ixScaled, 0, settings.cropResWidth);
        iyScaled = $BHXf$export$clampNumber(iyScaled, 0, settings.cropResHeight);
        updateCrop(ixScaled, iyScaled, 0, 0);

        dragCropPreviewHandler = function dragCropPreviewHandler(e) {
          let endX = Math.round((e.pageX - videoRect.left - playerRect.left) / videoRect.width * settings.cropResWidth);
          let endY = settings.cropResHeight;

          if (!verticalFill) {
            endY = Math.round((e.pageY - videoRect.top - playerRect.top) / videoRect.height * settings.cropResHeight);
          }

          endX = $BHXf$export$clampNumber(endX, 0, settings.cropResWidth);
          endY = $BHXf$export$clampNumber(endY, 0, settings.cropResHeight);
          let x, y, w, h;

          if (endX > ixScaled) {
            x = ixScaled;
            w = endX - x;
          } else {
            x = endX;
            w = ixScaled - x;
          }

          if (endY > iyScaled) {
            y = iyScaled;
            h = endY - y;
          } else {
            y = endY;
            h = iyScaled - y;
          }

          w = $BHXf$export$clampNumber(w, 0, settings.cropResWidth);
          h = $BHXf$export$clampNumber(h, 0, settings.cropResHeight);
          updateCrop(x, y, w, h);
        };

        window.addEventListener('pointermove', dragCropPreviewHandler);
        window.addEventListener('pointerup', endDraw, true); // exact event listener reference only added once so remove not required

        document.addEventListener('click', blockVideoPause, {
          once: true,
          capture: true
        });
      } else {
        finishDrawingCrop(prevCropString);
      }
    }

    function blockVideoPause(e) {
      e.stopImmediatePropagation();
    }

    function endDraw(e) {
      if (e.button === 0) {
        finishDrawingCrop(null, e.pointerId);
      } else {
        finishDrawingCrop(prevCropString, e.pointerId);
      }

      if (e.ctrlKey) {
        window.addEventListener('mousemove', cropOverlayHoverHandler, true);
      }
    }

    function finishDrawingCrop(prevCropString, pointerId) {
      if (pointerId) video.releasePointerCapture(pointerId);
      playerInfo.container.style.removeProperty('cursor');
      playerInfo.container.removeEventListener('pointerdown', beginDrawHandler, true);
      window.removeEventListener('pointermove', dragCropPreviewHandler);
      window.removeEventListener('pointerup', endDraw, true);
      dragCropPreviewHandler = null;
      isDrawingCrop = false;
      showPlayerControls();
      window.addEventListener('keydown', addCropOverlayHoverListener, true);

      if (prevCropString) {
        updateCropString(prevCropString);
        flashMessage('Drawing crop canceled', 'red');
      } else {
        cropInput.dispatchEvent(new Event('change'));
        flashMessage('Finished drawing crop', 'green');
      }
    }

    function updateCropString(cropString) {
      const _getCropComponents13 = getCropComponents(cropString),
            _getCropComponents14 = $XbmT$var$_slicedToArray(_getCropComponents13, 4),
            x = _getCropComponents14[0],
            y = _getCropComponents14[1],
            w = _getCropComponents14[2],
            h = _getCropComponents14[3];

      updateCrop(x, y, w, h, cropString);
    }

    function updateCrop(x, y, w, h, cropString) {
      if (!cropString) {
        cropString = `${x}:${y}:${w}:${h}`;
      }

      if (isMarkerPairSettingsEditorOpen) {
        cropInput.value = cropString;

        if (!wasGlobalSettingsEditorOpen) {
          markerPairs[prevSelectedMarkerPairIndex].crop = cropString;
        } else {
          settings.newMarkerCrop = cropString;
        }

        setCropOverlayDimensions(x, y, w, h);
        const cropAspectRatio = (w / h).toFixed(13);
        cropAspectRatioSpan && (cropAspectRatioSpan.textContent = cropAspectRatio);
      } else {
        throw new Error('No editor was open when trying to update crop.');
      }
    }

    let arrowKeyCropAdjustmentEnabled = false;

    function toggleArrowKeyCropAdjustment() {
      if (arrowKeyCropAdjustmentEnabled) {
        document.removeEventListener('keydown', arrowKeyCropAdjustmentHandler, true);
        flashMessage('Disabled crop adjustment with arrow keys', 'red');
        arrowKeyCropAdjustmentEnabled = false;
      } else {
        document.addEventListener('keydown', arrowKeyCropAdjustmentHandler, true);
        flashMessage('Enabled crop adjustment with arrow keys', 'green');
        arrowKeyCropAdjustmentEnabled = true;
      }
    }

    function arrowKeyCropAdjustmentHandler(ke) {
      if (isMarkerPairSettingsEditorOpen) {
        if (cropInput !== document.activeElement && ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].indexOf(ke.code) > -1) {
          ke.preventDefault();
          ke.stopImmediatePropagation();

          let _getCropComponents15 = getCropComponents(cropInput.value),
              _getCropComponents16 = $XbmT$var$_slicedToArray(_getCropComponents15, 4),
              x = _getCropComponents16[0],
              y = _getCropComponents16[1],
              w = _getCropComponents16[2],
              h = _getCropComponents16[3];

          let changeAmount;

          if (!ke.altKey && !ke.shiftKey) {
            changeAmount = 10;
          } else if (ke.altKey && !ke.shiftKey) {
            changeAmount = 1;
          } else if (!ke.altKey && ke.shiftKey) {
            changeAmount = 50;
          } else if (ke.altKey && ke.shiftKey) {
            changeAmount = 100;
          } // without modifiers move crop x/y offset
          // with ctrl key modifier expand/shrink crop width/height


          let cropTarget;

          if (!ke.ctrlKey) {
            switch (ke.code) {
              case 'ArrowUp':
                y -= changeAmount;
                cropTarget = 'y';
                break;

              case 'ArrowDown':
                y += changeAmount;
                cropTarget = 'y';
                break;

              case 'ArrowLeft':
                x -= changeAmount;
                cropTarget = 'x';
                break;

              case 'ArrowRight':
                x += changeAmount;
                cropTarget = 'x';
                break;
            }
          } else {
            switch (ke.code) {
              case 'ArrowUp':
                h -= changeAmount;
                cropTarget = 'h';
                break;

              case 'ArrowDown':
                h += changeAmount;
                cropTarget = 'h';
                break;

              case 'ArrowLeft':
                w -= changeAmount;
                cropTarget = 'w';
                break;

              case 'ArrowRight':
                w += changeAmount;
                cropTarget = 'w';
                break;
            }
          }

          const cropArray = clampCropArray([x, y, w, h], cropTarget);
          cropInput.value = cropArray.join(':');
          cropInput.dispatchEvent(new Event('change'));
        }
      }
    }

    function getCropComponents(cropString) {
      if (!cropString && isMarkerPairSettingsEditorOpen) {
        if (!wasGlobalSettingsEditorOpen && prevSelectedMarkerPairIndex != null) {
          cropString = markerPairs[prevSelectedMarkerPairIndex].crop;
        } else {
          cropString = settings.newMarkerCrop;
        }
      }

      if (!cropString) {
        console.error('No valid crop string to extract components from.');
        cropString = '0:0:iw:ih';
      }

      const cropArray = cropString.split(':').map(cropStringComponent => {
        let cropComponent;

        if (cropStringComponent === 'iw') {
          cropComponent = settings.cropResWidth;
        } else if (cropStringComponent === 'ih') {
          cropComponent = settings.cropResHeight;
        } else {
          cropComponent = parseInt(cropStringComponent, 10);
        }

        return cropComponent;
      });
      return cropArray;
    }

    let speedChartContainer;
    let speedChartCanvas = `<canvas id="speedChartCanvas" width="1600" height="900"></canvas>`;
    let speedChart;
    $JsLj$exports.Chart.helpers.merge($JsLj$exports.Chart.defaults.global, $ZMrW$export$global);

    function toggleSpeedChart() {
      if (isMarkerPairSettingsEditorOpen && !wasGlobalSettingsEditorOpen && prevSelectedMarkerPairIndex != null) {
        if (!speedChart) {
          isSpeedChartVisible = true;
          isSpeedChartEnabled = true;
          loadSpeedMap($ZMrW$export$options);
          speedChartContainer = document.createElement('div');
          speedChartContainer.setAttribute('id', 'speedChartContainer');
          speedChartContainer.setAttribute('style', 'width: 100%; height: calc(100% - 20px); position: relative; z-index: 12');
          speedChartContainer.innerHTML = speedChartCanvas;
          const videoContainer = document.getElementsByClassName('html5-video-container')[0];
          videoContainer.insertAdjacentElement('afterend', speedChartContainer);
          speedChart = new $JsLj$exports.Chart('speedChartCanvas', $ZMrW$export$options);
          speedChart.ctx.canvas.removeEventListener('wheel', speedChart.$zoom._wheelHandler);
          const wheelHandler = speedChart.$zoom._wheelHandler;

          speedChart.$zoom._wheelHandler = e => {
            if (e.ctrlKey && !e.altKey && !e.shiftKey) {
              wheelHandler(e);
            }
          };

          speedChart.ctx.canvas.addEventListener('wheel', speedChart.$zoom._wheelHandler);
          speedChart.ctx.canvas.addEventListener('contextmenu', e => {
            e.preventDefault();
            e.stopImmediatePropagation();
          }, true);
          speedChart.ctx.canvas.addEventListener('mouseup', speedChartContextMenuHandler, true);
          updateSpeedChartTimeAnnotation();
        } else {
          toggleSpeedChartVisibility();
          isSpeedChartEnabled = !isSpeedChartEnabled;
        }
      } else {
        flashMessage('Please open a marker pair editor before toggling the time-variable speed chart', 'olive');
      }
    }

    function speedChartContextMenuHandler(e) {
      e.preventDefault();
      e.stopImmediatePropagation(); // shift+right-click context menu opens screenshot tool in firefox 67.0.2

      if (e.button === 2 && !e.ctrlKey && !e.altKey && !e.shiftKey) {
        $XbmT$export$player.seekTo(speedChart.scales['x-axis-1'].getValueForPixel(e.offsetX));
      } else if (e.button === 2 && !e.ctrlKey && e.altKey && !e.shiftKey) {
        const start = speedChart.scales['x-axis-1'].getValueForPixel(e.offsetX);
        speedChart.config.options.annotation.annotations[1].value = start;
        markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.start = start;
        speedChart.update();
      } else if (e.button === 2 && e.ctrlKey && e.altKey && !e.shiftKey) {
        const end = speedChart.scales['x-axis-1'].getValueForPixel(e.offsetX);
        speedChart.config.options.annotation.annotations[2].value = end;
        markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.end = end;
        speedChart.update();
      }
    }

    let easingMode = 'linear';

    function toggleSpeedChartEasing() {
      if (speedChart) {
        if (easingMode === 'linear') {
          speedChart.data.datasets[0].lineTension = $ZMrW$export$cubicInOutTension;
          easingMode = 'cubicInOut';
        } else {
          speedChart.data.datasets[0].lineTension = 0;
          easingMode = 'linear';
        }

        speedChart.update();
      }
    }

    function toggleSpeedMapLoop() {
      if (isSpeedChartVisible && prevSelectedMarkerPairIndex != null) {
        if (markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.enabled) {
          markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.enabled = false;
          speedChart.config.options.annotation.annotations[1].borderColor = 'rgba(0, 255, 0, 0.4)';
          speedChart.config.options.annotation.annotations[2].borderColor = 'rgba(255, 215, 0, 0.4)';
          flashMessage('Speed chart looping disabled', 'red');
        } else {
          markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.enabled = true;
          speedChart.config.options.annotation.annotations[1].borderColor = 'rgba(0, 255, 0, 0.9)';
          speedChart.config.options.annotation.annotations[2].borderColor = 'rgba(255, 215, 0, 0.9)';
          flashMessage('Speed chart looping enabled', 'green');
        }

        speedChart.update();
      }
    }

    function resetSpeedMapLoop() {
      if (isSpeedChartVisible && prevSelectedMarkerPairIndex != null) {
        markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.start = undefined;
        markerPairs[prevSelectedMarkerPairIndex].speedMapLoop.end = undefined;
        speedChart.config.options.annotation.annotations[1].value = -1;
        speedChart.config.options.annotation.annotations[2].value = -1;
        speedChart.update();
      }
    }

    function loadSpeedMap(chartOrChartConfig) {
      if (chartOrChartConfig) {
        if (isMarkerPairSettingsEditorOpen && !wasGlobalSettingsEditorOpen && prevSelectedMarkerPairIndex != null) {
          const markerPair = markerPairs[prevSelectedMarkerPairIndex];
          let speedMap = markerPair.speedMap;
          chartOrChartConfig.data.datasets[0].data = speedMap;

          if (chartOrChartConfig instanceof $JsLj$exports.Chart) {
            updateSpeedChartBounds(chartOrChartConfig.config, markerPair.start, markerPair.end);
            speedChart.update();
          } else {
            updateSpeedChartBounds(chartOrChartConfig, markerPair.start, markerPair.end);
          }
        }
      }
    }

    let speedChartMinBound;
    let speedChartMaxBound;

    function updateSpeedChartBounds(chartConfig, start, end) {
      speedChartMinBound = start;
      speedChartMaxBound = end;
      chartConfig.options.scales.xAxes[0].ticks.min = start;
      chartConfig.options.scales.xAxes[0].ticks.max = end;
      chartConfig.options.plugins.zoom.pan.rangeMin.x = start;
      chartConfig.options.plugins.zoom.pan.rangeMax.x = end;
      chartConfig.options.plugins.zoom.zoom.rangeMin.x = start;
      chartConfig.options.plugins.zoom.zoom.rangeMax.x = end;
    }

    function updateSpeedChartTimeAnnotation() {
      const time = video.currentTime;

      if (speedChartMinBound <= time && time <= speedChartMaxBound) {
        speedChart.config.options.annotation.annotations[0].value = video.currentTime;
        speedChart.update();
      }

      if (isSpeedChartVisible) {
        requestAnimationFrame(updateSpeedChartTimeAnnotation);
      }
    }

    function updateAllMarkerPairSpeeds(newSpeed) {
      markerPairs.forEach(markerPair => {
        markerPair.speed = newSpeed;
        const speedMap = markerPair.speedMap;

        if (speedMap.length === 2 && speedMap[0].y === speedMap[1].y) {
          markerPair.speedMap[1].y = newSpeed;
        }

        markerPair.speedMap[0].y = newSpeed;
      });

      if (isMarkerPairSettingsEditorOpen) {
        if (wasGlobalSettingsEditorOpen) {
          const markerPairMergeListInput = document.getElementById('merge-list-input');
          markerPairMergeListInput.dispatchEvent(new Event('change'));
        } else {
          const speedInput = document.getElementById('speed-input');
          speedInput.value = newSpeed.toString();
          speedInput.dispatchEvent(new Event('change'));
        }
      }

      flashMessage(`All marker speeds updated to ${newSpeed}`, 'olive');
    }

    function updateAllMarkerPairCrops(newCrop) {
      markerPairs.forEach(markerPair => {
        markerPair.crop = newCrop;
      });

      if (isMarkerPairSettingsEditorOpen && !wasGlobalSettingsEditorOpen) {
        const cropInput = document.getElementById('crop-input');
        cropInput.value = newCrop;
        cropInput.dispatchEvent(new Event('change'));
      }

      flashMessage(`All marker crops updated to ${newCrop}`, 'olive');
    }

    function toggleMarkerPairEditorHandler(e) {
      const targetMarker = e.target;

      if (targetMarker && e.shiftKey) {
        toggleMarkerPairEditor(targetMarker);
      }
    }

    let isSpeedChartEnabled = false;

    function toggleMarkerPairEditor(targetMarker) {
      // if target marker is previously selected marker: toggle target on/off
      if (prevSelectedEndMarker === targetMarker && !wasGlobalSettingsEditorOpen) {
        isMarkerPairSettingsEditorOpen ? toggleOffMarkerPairEditor() : toggleOnMarkerPairEditor(targetMarker); // otherwise switching from a different marker pair or from global settings editor
      } else {
        // delete current settings editor appropriately
        if (isMarkerPairSettingsEditorOpen) {
          wasGlobalSettingsEditorOpen ? toggleOffGlobalSettingsEditor() : toggleOffMarkerPairEditor();
        } // create new marker pair settings editor


        toggleOnMarkerPairEditor(targetMarker);
      }
    }

    function toggleOnMarkerPairEditor(targetMarker) {
      prevSelectedEndMarker = targetMarker;
      prevSelectedMarkerPairIndex = parseInt(prevSelectedEndMarker.getAttribute('idx')) - 1;
      highlightSelectedMarkerPair(targetMarker);
      enableMarkerHotkeys(targetMarker);
      createMarkerPairEditor(targetMarker);
      addCropInputHotkeys();
      loadSpeedMap(speedChart);
      showCropOverlay();

      if (isSpeedChartEnabled) {
        showSpeedChart();
      }

      targetMarker.classList.add('selected-marker');
      targetMarker.previousElementSibling.classList.add('selected-marker');
      const markerPair = markerPairs[prevSelectedMarkerPairIndex];
      markerPair.startNumbering.classList.add('selectedMarkerNumbering');
      markerPair.endNumbering.classList.add('selectedMarkerNumbering');

      if (isAutoHideUnselectedMarkerPairsOn) {
        autoHideUnselectedMarkerPairsStyle = injectCSS(autoHideUnselectedMarkerPairsCSS, 'auto-hide-unselected-marker-pairs-css');
      }
    }

    function toggleOffMarkerPairEditor() {
      let hardHide = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
      deleteSettingsEditor();
      hideSelectedMarkerPairOverlay(hardHide);
      hideCropOverlay();
      hideSpeedChart();
      prevSelectedEndMarker.classList.remove('selected-marker');
      prevSelectedEndMarker.previousElementSibling.classList.remove('selected-marker');
      const markerPair = markerPairs[prevSelectedMarkerPairIndex];
      markerPair.startNumbering.classList.remove('selectedMarkerNumbering');
      markerPair.endNumbering.classList.remove('selectedMarkerNumbering');

      if (isAutoHideUnselectedMarkerPairsOn) {
        $BHXf$export$deleteElement(autoHideUnselectedMarkerPairsStyle);
      }
    }

    const autoHideUnselectedMarkerPairsCSS = `
    rect.marker {
      opacity: 0.25;
    }
    text.markerNumbering {
      opacity: 0.25;
    }

    rect.selected-marker {
      opacity: 1;
    }
    text.selectedMarkerNumbering {
      opacity: 1;
    }

    rect.marker.end-marker {
      pointer-events: none;
    }
    rect.selected-marker.end-marker {
      pointer-events: fill;
    }
    `;
    let autoHideUnselectedMarkerPairsStyle;
    let isAutoHideUnselectedMarkerPairsOn = false;

    function toggleAutoHideUnselectedMarkerPairs(e) {
      if (e.ctrlKey && !arrowKeyCropAdjustmentEnabled) {
        e.preventDefault();
        e.stopImmediatePropagation();

        if (!isAutoHideUnselectedMarkerPairsOn) {
          autoHideUnselectedMarkerPairsStyle = injectCSS(autoHideUnselectedMarkerPairsCSS, 'auto-hide-unselected-marker-pairs-css');
          isAutoHideUnselectedMarkerPairsOn = true;
          flashMessage('Auto-hiding of unselected marker pairs enabled', 'green');
        } else {
          $BHXf$export$deleteElement(autoHideUnselectedMarkerPairsStyle);
          isAutoHideUnselectedMarkerPairsOn = false;
          flashMessage('Auto-hiding of unselected marker pairs disabled', 'red');
        }
      }
    }

    let markerPairNumberInput;

    function createMarkerPairEditor(targetMarker) {
      const markerPairIndex = parseInt(targetMarker.getAttribute('idx'), 10) - 1;
      const markerPair = markerPairs[markerPairIndex];
      const startTime = $BHXf$export$toHHMMSSTrimmed(markerPair.start);
      const endTime = $BHXf$export$toHHMMSSTrimmed(markerPair.end);
      const speed = markerPair.speed;
      const duration = $BHXf$export$toHHMMSSTrimmed(markerPair.end - markerPair.start);
      const speedAdjustedDuration = $BHXf$export$toHHMMSSTrimmed((markerPair.end - markerPair.start) / speed);
      const crop = markerPair.crop;
      const cropInputValidation = `\\d+:\\d+:(\\d+|iw):(\\d+|ih)`;

      const _getCropComponents17 = getCropComponents(crop),
            _getCropComponents18 = $XbmT$var$_slicedToArray(_getCropComponents17, 4),
            x = _getCropComponents18[0],
            y = _getCropComponents18[1],
            w = _getCropComponents18[2],
            h = _getCropComponents18[3];

      const cropAspectRatio = (w / h).toFixed(13);
      const settingsEditorDiv = document.createElement('div');
      const overrides = markerPair.overrides;
      const vidstab = overrides.videoStabilization;
      const vidstabDesc = vidstab ? vidstab.desc : null;
      const vidstabDescGlobal = settings.videoStabilization ? `(${settings.videoStabilization.desc})` : '(Disabled)';
      const vidstabDynamicZoomEnabled = overrides.videoStabilizationDynamicZoom;
      const denoise = overrides.denoise;
      const denoiseDesc = denoise ? denoise.desc : null;
      const denoiseDescGlobal = settings.denoise ? `(${settings.denoise.desc})` : '(Disabled)';
      const overridesEditorDisplay = isExtraSettingsEditorEnabled ? 'block' : 'none';
      createCropOverlay(crop);
      settingsEditorDiv.setAttribute('id', 'settings-editor-div');
      settingsEditorDiv.innerHTML = `
      <fieldset class="settings-editor-panel marker-pair-settings-editor-highlighted-div">
        <legend class="marker-pair-settings-editor-highlighted-label">Marker Pair
          <input id="marker-pair-number-input"
            title="${$zc7V$export$Tooltips.markerPairNumberTooltip}"
            type="number" value="${markerPairIndex + 1}"
            step="1" min="1" max="${markerPairs.length}" style="width:3em" required>
          </input>
          /
          <span id="marker-pair-count-label">${markerPairs.length}</span>
          Settings\
        </legend>
        <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.speedTooltip}">
          <span>Speed</span>
          <input id="speed-input"type="number" placeholder="speed" value="${speed}" 
            step="0.05" min="0.05" max="2" style="min-width:4em" required></input>
        </div>
        <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.cropTooltip}">
          <span>Crop</span>
          <input id="crop-input" value="${crop}" pattern="${cropInputValidation}" 
          style="min-width:10em" required></input>
        </div>
        <div class="settings-editor-input-div settings-info-display">
          <span>Crop Aspect Ratio</span>
          <br>
          <span id="crop-aspect-ratio">${cropAspectRatio}</span>
        </div>
        <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.titlePrefixTooltip}">
          <span>Title Prefix</span>
          <input id="title-prefix-input" value="${overrides.titlePrefix != null ? overrides.titlePrefix : ''}" placeholder="None" style="min-width:10em;text-align:right"></input>
        </div>
        <div class="settings-editor-input-div settings-info-display">
          <span>Time:</span>
          <span id="start-time">${startTime}</span>
          <span> - </span>
          <span id="end-time">${endTime}</span>
          <br>
          <span>Duration: </span>
          <span id="duration">${duration} / ${markerPair.speed} = ${speedAdjustedDuration}</span>
        </div>
      </fieldset>
      <fieldset id="marker-pair-overrides" class="settings-editor-panel marker-pair-settings-editor-highlighted-div" style="display:${overridesEditorDisplay}">
        <legend class="marker-pair-settings-editor-highlighted-label">Overrides</legend>
        <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.audioTooltip}">
          <span>Audio</span>
          <select id="audio-input">
            <option ${overrides.audio ? 'selected' : ''}>Enabled</option>
            <option ${overrides.audio === false ? 'selected' : ''}>Disabled</option>
            <option value="Default" ${overrides.audio == null ? 'selected' : ''}>Inherit ${ternaryToString(settings.audio)}</option>
          </select>
        </div>
        <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.encodeSpeedTooltip}">
          <span>Encode Speed (0-5)</span>
          <input id="encode-speed-input" type="number" min="0" max="5" step="1" value="${overrides.encodeSpeed != null ? overrides.encodeSpeed : ''}" placeholder="${settings.encodeSpeed || 'Auto'}"  style="min-width:4em"></input>
        </div>
        <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.CRFTooltip}">
          <span>CRF (0-63)</span>
          <input id="crf-input" type="number" min="0" max="63" step="1" value="${overrides.crf != null ? overrides.crf : ''}" placeholder="${settings.crf != null ? settings.crf : 'Auto'}" "style="min-width:4em"></input>
        </div>
        <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.targetBitrateTooltip}">
          <span>Target Bitrate (kb/s)</span>
          <input id="target-max-bitrate-input" type="number" min="0" max="10e5" step="100" value="${overrides.targetMaxBitrate != null ? overrides.targetMaxBitrate : ''}" placeholder="${settings.targetMaxBitrate || 'Auto'}" "style="min-width:4em"></input>
        </div>
        <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.twoPassTooltip}">
          <span>Two-Pass</span>
          <select id="two-pass-input"> 
            <option ${overrides.twoPass ? 'selected' : ''}>Enabled</option>
            <option ${overrides.twoPass === false ? 'selected' : ''}>Disabled</option>
            <option value="Default" ${overrides.twoPass == null ? 'selected' : ''}>Inherit ${ternaryToString(settings.twoPass)}</option>
          </select>
        </div>
        <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.gammaTooltip}">
          <span>Gamma (0-4)</span>
          <input id="gamma-input" type="number" min="0.01" max="4.00" step="0.01" value="${overrides.gamma != null ? overrides.gamma : ''}" placeholder="${settings.gamma != null ? settings.gamma : '1'}" style="min-width:4em"></input>
        </div>
        <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.expandColorRangeTooltip}">
          <span>Expand Colors</span>
          <select id="expand-color-range-input"> 
            <option ${overrides.expandColorRange ? 'selected' : ''}>Enabled</option>
            <option ${overrides.expandColorRange === false ? 'selected' : ''}>Disabled</option>
            <option value="Default" ${overrides.expandColorRange == null ? 'selected' : ''}>Inherit ${ternaryToString(settings.expandColorRange)}</option>
          </select>
        </div>
        <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.denoiseTooltip}">
          <span>Denoise</span>
          <select id="denoise-input">
            <option ${denoiseDesc === 'Very Strong' ? 'selected' : ''}>Very Strong</option>
            <option ${denoiseDesc === 'Strong' ? 'selected' : ''}>Strong</option>
            <option ${denoiseDesc === 'Medium' ? 'selected' : ''}>Medium</option>
            <option ${denoiseDesc === 'Weak' ? 'selected' : ''}>Weak</option>
            <option ${denoiseDesc === 'Very Weak' ? 'selected' : ''}>Very Weak</option>
            <option value="Disabled" ${denoiseDesc == 'Disabled' ? 'selected' : ''}>Disabled</option>
            <option value="Inherit" ${denoiseDesc == null ? 'selected' : ''}>Inherit ${denoiseDescGlobal}</option>
          </select>
        </div>
        <div class="settings-editor-input-div" title="${$zc7V$export$Tooltips.speedMapTooltip}">
        <span>Speed Map</span>
            <select id="enable-speed-maps-input">
              <option ${overrides.enableSpeedMaps ? 'selected' : ''}>Enabled</option>
              <option ${overrides.enableSpeedMaps === false ? 'selected' : ''}>Disabled</option>
              <option value="Default" ${overrides.enableSpeedMaps == null ? 'selected' : ''}>Inherit ${ternaryToString(settings.enableSpeedMaps, '(Enabled)')}</option>
            </select>
        </div>
        <div class="settings-editor-input-div multi-input-div" title="${$zc7V$export$Tooltips.vidstabTooltip}">
          <div>
            <span>Stabilization</span>
            <select id="video-stabilization-input">
              <option ${vidstabDesc === 'Strongest' ? 'selected' : ''}>Strongest</option>
              <option ${vidstabDesc === 'Very Strong' ? 'selected' : ''}>Very Strong</option>
              <option ${vidstabDesc === 'Strong' ? 'selected' : ''}>Strong</option>
              <option ${vidstabDesc === 'Medium' ? 'selected' : ''}>Medium</option>
              <option ${vidstabDesc === 'Weak' ? 'selected' : ''}>Weak</option>
              <option ${vidstabDesc === 'Very Weak' ? 'selected' : ''}>Very Weak</option>
              <option value="Disabled" ${vidstabDesc == 'Disabled' ? 'selected' : ''}>Disabled</option>
              <option value="Inherit" ${vidstabDesc == null ? 'selected' : ''}>Inherit ${vidstabDescGlobal}</option>
            </select>
          </div>
          <div title="${$zc7V$export$Tooltips.dynamicZoomTooltip}">
            <span>Dynamic Zoom</span>
            <select id="video-stabilization-dynamic-zoom-input"> 
              <option ${vidstabDynamicZoomEnabled ? 'selected' : ''}>Enabled</option>
              <option ${vidstabDynamicZoomEnabled === false ? 'selected' : ''}>Disabled</option>
              <option value="Default" ${vidstabDynamicZoomEnabled == null ? 'selected' : ''}>Inherit ${ternaryToString(settings.videoStabilizationDynamicZoom)}</option>
            </select>
          </div>
        </div>
        <div class="settings-editor-input-div multi-input-div" title="${$zc7V$export$Tooltips.loopTooltip}">
          <div>
            <span>Loop</span>
            <select id="loop-input">
              <option ${overrides.loop === 'fwrev' ? 'selected' : ''}>fwrev</option>
              <option ${overrides.loop === 'fade' ? 'selected' : ''}>fade</option>
              <option ${overrides.loop === 'none' ? 'selected' : ''}>none</option>
              <option value="Default" ${overrides.loop == null ? 'selected' : ''}>Inherit ${settings.loop != null ? `(${settings.loop})` : '(none)'}</option>
            </select>
          </div>
          <div title="${$zc7V$export$Tooltips.fadeDurationTooltip}">
            <span>Fade Duration</span>
            <input id="fade-duration-input" type="number" min="0.1" step="0.1" value="${overrides.fadeDuration != null ? overrides.fadeDuration : ''}" placeholder="${settings.fadeDuration != null ? settings.fadeDuration : '0.5'}" style="width:7em"></input>
          </div>
        </div>
      </fieldset>
      `;
      updateSettingsEditorHook();
      settingsEditorHook.insertAdjacentElement('beforebegin', settingsEditorDiv);
      const inputConfigs = [['speed-input', 'speed', 'number'], ['crop-input', 'crop', 'string']];
      addSettingsInputListeners(inputConfigs, markerPairs[markerPairIndex], true);
      const overrideInputConfigs = [['title-prefix-input', 'titlePrefix', 'string'], ['gamma-input', 'gamma', 'number'], ['encode-speed-input', 'encodeSpeed', 'number'], ['crf-input', 'crf', 'number'], ['target-max-bitrate-input', 'targetMaxBitrate', 'number'], ['two-pass-input', 'twoPass', 'ternary'], ['audio-input', 'audio', 'ternary'], ['expand-color-range-input', 'expandColorRange', 'ternary'], ['enable-speed-maps-input', 'enableSpeedMaps', 'ternary'], ['denoise-input', 'denoise', 'preset'], ['video-stabilization-input', 'videoStabilization', 'preset'], ['video-stabilization-dynamic-zoom-input', 'videoStabilizationDynamicZoom', 'ternary'], ['loop-input', 'loop', 'inheritableString'], ['fade-duration-input', 'fadeDuration', 'number']];
      addSettingsInputListeners(overrideInputConfigs, markerPairs[markerPairIndex].overrides, true);
      markerPairNumberInput = document.getElementById('marker-pair-number-input');
      markerPairNumberInput.addEventListener('change', markerPairNumberInputHandler);
      cropInput = document.getElementById('crop-input');
      cropAspectRatioSpan = document.getElementById('crop-aspect-ratio');
      isMarkerPairSettingsEditorOpen = true;
      wasGlobalSettingsEditorOpen = false;
      highlightModifiedSettings(inputConfigs, markerPairs[markerPairIndex]);
      highlightModifiedSettings(overrideInputConfigs, markerPairs[markerPairIndex].overrides);
    }

    function ternaryToString(ternary, def) {
      if (ternary == null) {
        return def != null ? def : '(Disabled)';
      } else if (ternary === true) {
        return '(Enabled)';
      } else if (ternary === false) {
        return '(Disabled)';
      } else {
        return null;
      }
    }

    function markerPairNumberInputHandler(e) {
      const markerPair = markerPairs[prevSelectedMarkerPairIndex];
      const startNumbering = markerPair.startNumbering;
      const endNumbering = markerPair.endNumbering;
      const newIdx = e.target.value - 1;
      markerPairs.splice(newIdx, 0, ...markerPairs.splice(prevSelectedMarkerPairIndex, 1));
      let targetMarkerRect = markersSvg.children[newIdx * 2];
      let targetStartNumbering = startMarkerNumberings.children[newIdx];
      let targetEndNumbering = endMarkerNumberings.children[newIdx]; // if target succeedes current marker pair, move pair after target

      if (newIdx > prevSelectedMarkerPairIndex) {
        targetMarkerRect = targetMarkerRect.nextElementSibling.nextElementSibling;
        targetStartNumbering = targetStartNumbering.nextElementSibling;
        targetEndNumbering = targetEndNumbering.nextElementSibling;
      }

      const prevSelectedStartMarker = prevSelectedEndMarker.previousElementSibling; // if target precedes current marker pair, move pair before target

      markersSvg.insertBefore(prevSelectedStartMarker, targetMarkerRect);
      markersSvg.insertBefore(prevSelectedEndMarker, targetMarkerRect);
      startMarkerNumberings.insertBefore(startNumbering, targetStartNumbering);
      endMarkerNumberings.insertBefore(endNumbering, targetEndNumbering);
      renumberMarkerPairs();
      prevSelectedMarkerPairIndex = newIdx;
    }

    function highlightModifiedSettings(inputs, target) {
      if (isMarkerPairSettingsEditorOpen) {
        const markerPairSettingsLabelHighlight = 'marker-pair-settings-editor-highlighted-label';
        const globalSettingsLabelHighlight = 'global-settings-editor-highlighted-label';
        let markerPair;

        if (!wasGlobalSettingsEditorOpen && prevSelectedMarkerPairIndex != null) {
          markerPair = markerPairs[prevSelectedMarkerPairIndex];
        }

        inputs.forEach(input => {
          const id = input[0];
          const targetProperty = input[1];
          const inputElem = document.getElementById(id);
          const storedTargetValue = target[targetProperty];
          let label = inputElem.previousElementSibling;
          if (id === 'rotate-90-clock' || id === 'rotate-90-counterclock') label = inputElem.parentElement.getElementsByTagName('span')[0];
          const shouldRemoveHighlight = storedTargetValue == null || storedTargetValue === '' || id === 'title-suffix-input' && storedTargetValue == `[${settings.videoID}]` || markerPair && id === 'speed-input' && storedTargetValue === 1 && !isVariableSpeed(markerPair.speedMap) || id === 'crop-input' && (storedTargetValue === '0:0:iw:ih' || storedTargetValue === `0:0:${settings.cropResWidth}:${settings.cropResHeight}`) || id === 'rotate-0';

          if (shouldRemoveHighlight) {
            label.classList.remove(globalSettingsLabelHighlight);
            label.classList.remove(markerPairSettingsLabelHighlight);
          } else {
            if (target === settings) {
              label.classList.add(globalSettingsLabelHighlight);
            } else {
              if (storedTargetValue === settings[targetProperty]) {
                label.classList.add(globalSettingsLabelHighlight);
                label.classList.remove(markerPairSettingsLabelHighlight);
              } else {
                label.classList.add(markerPairSettingsLabelHighlight);
                label.classList.remove(globalSettingsLabelHighlight);
              }
            }
          }
        });
      }
    }

    function enableMarkerHotkeys(endMarker) {
      markerHotkeysEnabled = true;
      enableMarkerHotkeys.endMarker = endMarker;
      enableMarkerHotkeys.startMarker = endMarker.previousSibling;
    }

    function moveMarker(marker, newTime) {
      let storeHistory = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
      const type = marker.getAttribute('type');
      const idx = parseInt(marker.getAttribute('idx')) - 1;
      const markerPair = markerPairs[idx];
      let fromTime = type === 'start' ? markerPair.start : markerPair.end;
      const toTime = newTime != null ? newTime : video.currentTime;
      const progress_pos = toTime / playerInfo.duration * 100;
      const markerTimeSpan = document.getElementById(`${type}-time`);
      const speedMap = markerPair.speedMap;

      if (type === 'start' && toTime >= markerPair.end) {
        flashMessage('Start marker cannot be placed after or at end marker', 'red');
        return;
      }

      if (type === 'end' && toTime <= markerPair.start) {
        flashMessage('End marker cannot be placed before or at start marker', 'red');
        return;
      }

      marker.setAttribute('x', `${progress_pos}%`);
      markerPair[type] = toTime;

      if (type === 'start') {
        selectedStartMarkerOverlay.setAttribute('x', `${progress_pos}%`);
        markerPair.startNumbering.setAttribute('x', `${progress_pos}%`);
        speedMap[0].x = toTime;
        markerPair.speedMap = speedMap.filter(speedPoint => {
          return speedPoint.x >= toTime;
        });
      } else if (type === 'end') {
        speedMap[speedMap.length - 1].x = toTime;
        selectedEndMarkerOverlay.setAttribute('x', `${progress_pos}%`);
        markerPair.endNumbering.setAttribute('x', `${progress_pos}%`);
        markerPair.speedMap = speedMap.filter(speedPoint => {
          return speedPoint.x <= toTime;
        });
      }

      markerTimeSpan.textContent = `${$BHXf$export$toHHMMSSTrimmed(toTime)}`;

      if (speedChart) {
        speedChart.config.data.datasets[0].data = markerPair.speedMap;
        updateSpeedChartBounds(speedChart.config, markerPair.start, markerPair.end);
        speedChart.update();
      }

      updateMarkerPairDuration(markerPair);
      if (storeHistory) markerPair.moveHistory.undos.push({
        marker,
        fromTime,
        toTime
      });
    }

    function undoMarkerMove() {
      if (isMarkerPairSettingsEditorOpen && !wasGlobalSettingsEditorOpen && prevSelectedMarkerPairIndex != null && markerPairs[prevSelectedMarkerPairIndex].moveHistory.undos.length > 0) {
        const moveHistory = markerPairs[prevSelectedMarkerPairIndex].moveHistory;
        const lastMarkerMove = moveHistory.undos.pop();
        moveHistory.redos.push(lastMarkerMove);
        moveMarker(lastMarkerMove.marker, lastMarkerMove.fromTime, false);
      }
    }

    function redoMarkerMove() {
      if (isMarkerPairSettingsEditorOpen && !wasGlobalSettingsEditorOpen && prevSelectedMarkerPairIndex != null && markerPairs[prevSelectedMarkerPairIndex].moveHistory.redos.length > 0) {
        const moveHistory = markerPairs[prevSelectedMarkerPairIndex].moveHistory;
        const lastMarkerMoveUndo = moveHistory.redos.pop();
        moveMarker(lastMarkerMoveUndo.marker, lastMarkerMoveUndo.toTime, true);
      }
    }

    function deleteMarkerPair(idx) {
      if (idx == null) idx = prevSelectedMarkerPairIndex;
      const markerPair = markerPairs[idx];
      const me = new MouseEvent('mouseover', {
        shiftKey: true
      });
      enableMarkerHotkeys.endMarker.dispatchEvent(me);
      $BHXf$export$deleteElement(enableMarkerHotkeys.endMarker);
      $BHXf$export$deleteElement(enableMarkerHotkeys.startMarker);
      $BHXf$export$deleteElement(markerPair.startNumbering);
      $BHXf$export$deleteElement(markerPair.endNumbering);
      hideSelectedMarkerPairOverlay(true);
      renumberMarkerPairs();
      markerPairs.splice(idx, 1);
      clearPrevSelectedMarkerPairReferences();
    }

    function clearPrevSelectedMarkerPairReferences() {
      prevSelectedMarkerPairIndex = null;
      prevSelectedEndMarker = null;
      enableMarkerHotkeys.startMarker = null;
      enableMarkerHotkeys.endMarker = null;
      markerHotkeysEnabled = false;
    }

    let selectedStartMarkerOverlay;
    let selectedEndMarkerOverlay;

    function highlightSelectedMarkerPair(currentMarker) {
      if (!selectedStartMarkerOverlay) {
        selectedStartMarkerOverlay = document.getElementById('selected-start-marker-overlay');
      }

      if (!selectedEndMarkerOverlay) {
        selectedEndMarkerOverlay = document.getElementById('selected-end-marker-overlay');
      }

      const startMarker = currentMarker.previousSibling;
      selectedStartMarkerOverlay.setAttribute('x', startMarker.getAttribute('x'));
      selectedEndMarkerOverlay.setAttribute('x', currentMarker.getAttribute('x'));
      selectedStartMarkerOverlay.classList.remove('selected-marker-overlay-hidden');
      selectedEndMarkerOverlay.classList.remove('selected-marker-overlay-hidden');
      selectedMarkerPairOverlay.style.display = 'block';
    }

    function updateMarkerPairDuration(markerPair) {
      const speedAdjustedDurationSpan = document.getElementById('duration');
      const duration = markerPair.end - markerPair.start;
      const durationHHMMSS = $BHXf$export$toHHMMSSTrimmed(duration);
      const outputDuration = getOutputDuration(markerPair.speedMap);
      const outputDurationHHMMSS = $BHXf$export$toHHMMSSTrimmed(outputDuration);
      speedAdjustedDurationSpan.textContent = `${durationHHMMSS} (${outputDurationHHMMSS})`;
      markerPair.outputDuration = outputDuration;
    }

    function getOutputDuration(speedMap) {
      let outputDuration = 0;
      const fps = getFPS();
      const frameDur = 1 / fps;
      const nSects = speedMap.length - 1; // Account for marker pair start time as trim filter sets start time to ~0

      const speedMapStartTime = speedMap[0].x; // Account for first input frame delay due to potentially imprecise trim

      const startt = Math.ceil(speedMapStartTime / frameDur) * frameDur - speedMapStartTime;

      for (let sect = 0; sect < nSects; ++sect) {
        const left = speedMap[sect];
        const right = speedMap[sect + 1];
        const startSpeed = left.y;
        const endSpeed = right.y;
        const speedChange = endSpeed - startSpeed;
        const sectStart = left.x - speedMapStartTime - startt;
        let sectEnd = right.x - speedMapStartTime - startt; // Account for last input frame delay due to potentially imprecise trim

        if (sect === nSects - 1) {
          sectEnd = Math.floor(right['x'] / frameDur) * frameDur; // When trim is frame-precise, the frame that begins at the marker pair end time is not included

          if (right.x - sectEnd < 1e-10) sectEnd = sectEnd - frameDur;
          sectEnd = sectEnd - speedMapStartTime - startt;
          sectEnd = Math.floor(sectEnd * 1000000) / 1000000;
        }

        const sectDuration = sectEnd - sectStart;
        if (sectDuration === 0) continue;
        const m = speedChange / sectDuration;
        const b = startSpeed - m * sectStart;

        if (speedChange === 0) {
          outputDuration += sectDuration / endSpeed;
        } else {
          // Integrate the reciprocal of the linear time vs speed function for the current section
          outputDuration += 1 / m * (Math.log(Math.abs(m * sectEnd + b)) - Math.log(Math.abs(m * sectStart + b)));
        }
      } // Each output frame time is rounded to the nearest multiple of a frame's duration at the given fps


      outputDuration = Math.round(outputDuration / frameDur) * frameDur; // The last included frame is held for a single frame's duration

      outputDuration += frameDur;
      outputDuration = Math.round(outputDuration * 1000) / 1000;
      return outputDuration;
    }

    function renumberMarkerPairs() {
      const markersSvg = document.getElementById('markers-svg');
      markersSvg.childNodes.forEach((markerRect, idx) => {
        // renumber markers by pair starting with index 1
        const newIdx = Math.floor((idx + 2) / 2);
        markerRect.setAttribute('idx', newIdx);
      });
      startMarkerNumberings.childNodes.forEach((startNumbering, idx) => {
        const newIdx = idx + 1;
        startNumbering.setAttribute('idx', newIdx);
        startNumbering.textContent = newIdx.toString();
      });
      endMarkerNumberings.childNodes.forEach((endNumbering, idx) => {
        const newIdx = idx + 1;
        endNumbering.setAttribute('idx', newIdx);
        endNumbering.textContent = newIdx.toString();
      });
    }

    function hideSelectedMarkerPairOverlay() {
      let hardHide = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;

      if (hardHide) {
        selectedMarkerPairOverlay.style.display = 'none';
      } else {
        selectedStartMarkerOverlay.classList.add('selected-marker-overlay-hidden');
        selectedEndMarkerOverlay.classList.add('selected-marker-overlay-hidden');
      }
    }

    function showSpeedChart() {
      if (speedChartContainer) {
        if (isDrawingCrop) {
          finishDrawingCrop();
        }

        speedChartContainer.style.display = 'block';
        isSpeedChartVisible = true;
        requestAnimationFrame(updateSpeedChartTimeAnnotation);
      }
    }

    function hideSpeedChart() {
      if (speedChartContainer) {
        speedChartContainer.style.display = 'none';
        isSpeedChartVisible = false;
      }
    }

    function toggleSpeedChartVisibility() {
      if (!isSpeedChartVisible) {
        showSpeedChart();
      } else {
        hideSpeedChart();
      }
    }

    function deleteSettingsEditor() {
      const settingsEditorDiv = document.getElementById('settings-editor-div');
      hideCropOverlay();
      $BHXf$export$deleteElement(settingsEditorDiv);
      isMarkerPairSettingsEditorOpen = false;
      wasGlobalSettingsEditorOpen = false;
      markerHotkeysEnabled = false;
    }

    let isExtraSettingsEditorEnabled = false;

    function toggleMarkerPairOverridesEditor() {
      if (isMarkerPairSettingsEditorOpen) {
        const markerPairOverridesEditor = document.getElementById('marker-pair-overrides');

        if (markerPairOverridesEditor) {
          if (markerPairOverridesEditor.style.display === 'none') {
            markerPairOverridesEditor.style.display = 'block';
            isExtraSettingsEditorEnabled = true;
          } else {
            markerPairOverridesEditor.style.display = 'none';
            isExtraSettingsEditorEnabled = false;
          }
        }

        const globalEncodeSettingsEditor = document.getElementById('global-encode-settings');

        if (globalEncodeSettingsEditor) {
          if (globalEncodeSettingsEditor.style.display === 'none') {
            globalEncodeSettingsEditor.style.display = 'block';
            isExtraSettingsEditorEnabled = true;
          } else if (globalEncodeSettingsEditor.style.display === 'block') {
            globalEncodeSettingsEditor.style.display = 'none';
            isExtraSettingsEditorEnabled = false;
          }
        }
      }
    }

    function saveAuthServerScript() {
      const authScript = `\
import json
import re
from urllib.parse import urlencode, urlparse, parse_qs
from http.server import HTTPServer, BaseHTTPRequestHandler

CLIENT_ID = 'XXXX'
REDIRECT_URI = 'http://127.0.0.1:4443/yt_clipper?'

BROWSER_BASED_AUTH_ENDPOINT = f'https://gfycat.com/oauth/authorize?client_id={CLIENT_ID}&scope=all&state=yt_clipper&response_type=token&redirect_uri={REDIRECT_URI}'

REDIRECT_PAGE_BODY = b'''
<body>
    <script>
        let url = window.location.href;
        url = url.replace('?','&');
        url = url.replace('#','?access-token=');
        window.open(url,'_self');
    </script>
</body>
'''

COMPLETE_AUTH_PAGE_BODY = b'''
<body>
    <span>
        Please close this window and return to yt_clipper.
    </span>
</body>
'''


class getServer(BaseHTTPRequestHandler):
    redirected = -1

    def do_GET(self):
        print(self.path)
        if re.match('/yt_clipper*', self.path):
            if getServer.redirected == -1:
                self.send_response(200)
                self.end_headers()
                self.wfile.write(REDIRECT_PAGE_BODY)
                getServer.redirected = 0
            elif getServer.redirected == 0:
                self.send_response(200)
                self.end_headers()
                self.wfile.write(COMPLETE_AUTH_PAGE_BODY)
                getServer.query = parse_qs(urlparse(self.path).query)
                getServer.redirected = 1
            elif getServer.redirected == 1:
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Access-Control-Allow-Origin', '*')
                self.end_headers()
                self.wfile.write(json.dumps(getServer.query).encode())


httpd = HTTPServer(('localhost', 4443), getServer)
httpd.serve_forever()
`;
      const blob = new Blob([authScript], {
        type: 'text/plain;charset=utf-8'
      });
      $oSxG$exports.saveAs(blob, `yt_clipper_auth.py`);
    }

    function buildGfyRequests(markers, url) {
      return markers.map((marker, idx) => {
        const start = marker.start;
        const end = marker.end;
        const speed = marker.speed;

        let _marker$crop$split = marker.crop.split(':'),
            _marker$crop$split2 = $XbmT$var$_slicedToArray(_marker$crop$split, 4),
            x = _marker$crop$split2[0],
            y = _marker$crop$split2[1],
            w = _marker$crop$split2[2],
            h = _marker$crop$split2[3];

        w = w === 'iw' ? settings.cropResWidth.toString() : w;
        h = h === 'ih' ? settings.cropResHeight.toString() : h;
        let crop = [x, y, w, h].map(num => parseInt(num, 10));
        const startHHMMSS = $BHXf$export$toHHMMSS(start).split(':');
        const startHH = startHHMMSS[0];
        const startMM = startHHMMSS[1];
        const startSS = startHHMMSS[2];
        const duration = end - start;
        let req = {
          fetchUrl: url,
          title: `${settings.titleSuffix}-${idx + 1}`,
          fetchHours: startHH,
          fetchMinutes: startMM,
          fetchSeconds: startSS,
          noMd5: 'false',
          cut: {
            start,
            duration
          },
          speed,
          crop: {
            x: crop[0],
            y: crop[1],
            w: crop[2],
            h: crop[3]
          }
        };
        return req;
      });
    }

    function requestGfycatAuth() {
      const authPage = window.open(BROWSER_BASED_AUTH_ENDPOINT);
      const timer = setInterval(() => {
        if (authPage.closed) {
          clearInterval(timer);
          getAccessToken();
        }
      }, 2500);
    }

    function getAccessToken() {
      return new Promise(() => {
        fetch(REDIRECT_URI, {
          mode: 'cors'
        }).then(response => {
          return response.json();
        }).then(json => {
          const accessToken = json['access-token'][0];
          console.log(accessToken);
          sendGfyRequests(playerInfo.url, accessToken);
        }).catch(error => console.error(error));
      });
    }

    function sendGfyRequests(url, accessToken) {
      if (markerPairs.length > 0) {
        const markdown = toggleUploadStatus();
        const reqs = buildGfyRequests(markerPairs, url).map((req, idx) => {
          return buildGfyRequestPromise(req, idx, accessToken);
        });
        Promise.all(reqs).then(gfynames => {
          console.log(reqs);
          console.log(gfynames);
          checkGfysCompletedId = setInterval(checkGfysCompleted, 5000, gfynames, markdown);
        });
      }
    }

    function buildGfyRequestPromise(reqData, idx, accessToken) {
      return new Promise((resolve, reject) => {
        postData('https://api.gfycat.com/v1/gfycats', reqData, accessToken).then(resp => {
          links.push(`(${settings.titleSuffix}-${idx})[https://gfycat.com/${resp.gfyname}]`);
          resolve(resp.gfyname);
        }).catch(error => reject(error));
      });
    }

    function checkGfysCompleted(gfynames, markdown) {
      const gfyStatuses = gfynames.map(gfyname => {
        return checkGfyStatus(gfyname, markdown).then(isComplete => {
          return isComplete;
        });
      });
      Promise.all(gfyStatuses).then(gfyStatuses => {
        areGfysCompleted(gfyStatuses).then(() => insertMarkdown(markdown));
      }).catch(() => console.log('gfys not yet completed'));
    }

    function toggleUploadStatus() {
      const meta = document.getElementById('meta');
      const markdown = document.createElement('textarea');
      meta.insertAdjacentElement('beforebegin', markdown);
      $BHXf$export$setAttributes(markdown, {
        id: 'markdown',
        style: 'color:grey;width:600px;height:100px;',
        spellcheck: false
      });
      markdown.textContent = 'Upload initiated. Progress updates will begin shortly.\n';
      return markdown;
    }

    function updateUploadStatus(markdown, status, gfyname) {
      if (markdown) {
        markdown.textContent += `${gfyname} progress: ${status.progress}\n`;
        markdown.scrollTop = markdown.scrollHeight;
      }
    }

    function insertMarkdown(markdown) {
      if (markdown) {
        markdown.textContent = links.join('\n');
        window.clearInterval(checkGfysCompletedId);
      }
    }

    function areGfysCompleted(gfyStatuses) {
      return new Promise((resolve, reject) => {
        if (gfyStatuses.every(Boolean)) {
          resolve();
        } else {
          reject();
        }
      });
    }

    function checkGfyStatus(gfyname, markdown) {
      return new Promise((resolve, reject) => {
        fetch(`https://api.gfycat.com/v1/gfycats/fetch/status/${gfyname}`).then(response => {
          return response.json();
        }).then(myJson => {
          updateUploadStatus(markdown, myJson, gfyname);
          myJson.task === 'complete' ? resolve(true) : reject(false);
        }).catch(error => console.error(error));
      });
    }

    function postData(url, data, accessToken) {
      const auth = accessToken ? `Bearer ${accessToken}` : null;
      const req = {
        body: JSON.stringify(data),
        cache: 'no-cache',
        credentials: 'omit',
        headers: {
          'content-type': 'application/json'
        },
        method: 'POST',
        mode: 'cors',
        redirect: 'follow',
        referrer: 'no-referrer'
      };

      if (auth) {
        req.headers.Authorization = auth;
      }

      console.log(req);
      return fetch(url, req).then(response => response.json()); // parses response to JSON
    }
  }
})();

if (typeof exports === "object" && typeof module !== "undefined") {
  // CommonJS
  module.exports = $XbmT$exports;
} else if (typeof define === "function" && define.amd) {
  // RequireJS
  define(function () {
    return $XbmT$exports;
  });
}
})();