4chan sounds player

A player designed for 4chan sounds threads.

当前为 2021-01-02 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         4chan sounds player
// @version      3.2.1
// @namespace    rccom
// @description  A player designed for 4chan sounds threads.
// @author       RCC
// @match        *://boards.4chan.org/*
// @match        *://boards.4channel.org/*
// @match        *://desuarchive.org/*
// @match        *://arch.b4k.co/*
// @match        *://archived.moe/*
// @match        *://warosu.org/*
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.xmlHttpRequest
// @grant        GM_addValueChangeListener
// @connect      4chan.org
// @connect      4channel.org
// @connect      a.4cdn.org
// @connect      desu-usergeneratedcontent.xyz
// @connect      arch-img.b4k.co
// @connect      4cdn.org
// @connect      a.pomf.cat
// @connect      pomf.cat
// @connect      files.catbox.moe
// @connect      catbox.moe
// @connect      share.dmca.gripe
// @connect      z.zz.ht
// @connect      zz.ht
// @connect      too.lewd.se
// @connect      lewd.se
// @connect      *
// @run-at       document-start
// ==/UserScript==


/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// create a fake namespace object
/******/ 	// mode & 1: value is a module id, require it
/******/ 	// mode & 2: merge all properties of value into the ns
/******/ 	// mode & 4: return value when already ns object
/******/ 	// mode & 8|1: behave like require
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = "./src/main.js");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./node_modules/bootstrap-icons/icons/arrow-clockwise.svg":
/*!****************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/arrow-clockwise.svg ***!
  \****************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-arrow-clockwise\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z\"/>\n  <path d=\"M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/arrow-repeat.svg":
/*!*************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/arrow-repeat.svg ***!
  \*************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-arrow-repeat\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path d=\"M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z\"/>\n  <path fill-rule=\"evenodd\" d=\"M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/arrows-collapse.svg":
/*!****************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/arrows-collapse.svg ***!
  \****************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-arrows-collapse\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M1 8a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13A.5.5 0 0 1 1 8zm7-8a.5.5 0 0 1 .5.5v3.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L7.5 4.293V.5A.5.5 0 0 1 8 0zm-.5 11.707l-1.146 1.147a.5.5 0 0 1-.708-.708l2-2a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 11.707V15.5a.5.5 0 0 1-1 0v-3.793z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/arrows-expand.svg":
/*!**************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/arrows-expand.svg ***!
  \**************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-arrows-expand\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M1 8a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13A.5.5 0 0 1 1 8zM7.646.146a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 1.707V5.5a.5.5 0 0 1-1 0V1.707L6.354 2.854a.5.5 0 1 1-.708-.708l2-2zM8 10a.5.5 0 0 1 .5.5v3.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 0 1 .708-.708L7.5 14.293V10.5A.5.5 0 0 1 8 10z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/bootstrap-reboot.svg":
/*!*****************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/bootstrap-reboot.svg ***!
  \*****************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-bootstrap-reboot\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M1.161 8a6.84 6.84 0 1 0 6.842-6.84.58.58 0 0 1 0-1.16 8 8 0 1 1-6.556 3.412l-.663-.577a.58.58 0 0 1 .227-.997l2.52-.69a.58.58 0 0 1 .728.633l-.332 2.592a.58.58 0 0 1-.956.364l-.643-.56A6.812 6.812 0 0 0 1.16 8zm5.48-.079V5.277h1.57c.881 0 1.416.499 1.416 1.32 0 .84-.504 1.324-1.386 1.324h-1.6zm0 3.75V8.843h1.57l1.498 2.828h1.314L9.377 8.665c.897-.3 1.427-1.106 1.427-2.1 0-1.37-.943-2.246-2.456-2.246H5.5v7.352h1.141z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/chat-right-quote.svg":
/*!*****************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/chat-right-quote.svg ***!
  \*****************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-chat-right-quote\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M2 1h12a1 1 0 0 1 1 1v11.586l-2-2A2 2 0 0 0 11.586 11H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zm12-1a2 2 0 0 1 2 2v12.793a.5.5 0 0 1-.854.353l-2.853-2.853a1 1 0 0 0-.707-.293H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12z\"/>\n  <path fill-rule=\"evenodd\" d=\"M7.066 4.76A1.665 1.665 0 0 0 4 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112zm4 0A1.665 1.665 0 0 0 8 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/chevron-down.svg":
/*!*************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/chevron-down.svg ***!
  \*************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-chevron-down\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/file-earmark-image.svg":
/*!*******************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/file-earmark-image.svg ***!
  \*******************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-file-earmark-image\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M12 16a2 2 0 0 0 2-2V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8zM3 2a1 1 0 0 1 1-1h5.5v2A1.5 1.5 0 0 0 11 4.5h2V10l-2.083-2.083a.5.5 0 0 0-.76.063L8 11 5.835 9.7a.5.5 0 0 0-.611.076L3 12V2z\"/>\n  <path fill-rule=\"evenodd\" d=\"M6.502 7a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/file-earmark-music.svg":
/*!*******************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/file-earmark-music.svg ***!
  \*******************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-file-earmark-music\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path d=\"M4 0h5.5v1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h1V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2z\"/>\n  <path d=\"M9.5 3V0L14 4.5h-3A1.5 1.5 0 0 1 9.5 3z\"/>\n  <path fill-rule=\"evenodd\" d=\"M9.757 5.67A1 1 0 0 1 11 6.64v1.75l-2 .5v3.61c0 .495-.301.883-.662 1.123C7.974 13.866 7.499 14 7 14c-.5 0-.974-.134-1.338-.377-.36-.24-.662-.628-.662-1.123s.301-.883.662-1.123C6.026 11.134 6.501 11 7 11c.356 0 .7.068 1 .196V6.89a1 1 0 0 1 .757-.97l1-.25z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/filter.svg":
/*!*******************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/filter.svg ***!
  \*******************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-filter\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/fullscreen-exit.svg":
/*!****************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/fullscreen-exit.svg ***!
  \****************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-fullscreen-exit\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M5.5 0a.5.5 0 0 1 .5.5v4A1.5 1.5 0 0 1 4.5 6h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5zm5 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 10 4.5v-4a.5.5 0 0 1 .5-.5zM0 10.5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 6 11.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5zm10 1a1.5 1.5 0 0 1 1.5-1.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/fullscreen.svg":
/*!***********************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/fullscreen.svg ***!
  \***********************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-fullscreen\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M1.5 1a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4A1.5 1.5 0 0 1 1.5 0h4a.5.5 0 0 1 0 1h-4zM10 .5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 16 1.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5zM.5 10a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 0 14.5v-4a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v4a1.5 1.5 0 0 1-1.5 1.5h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/gear.svg":
/*!*****************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/gear.svg ***!
  \*****************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-gear\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M8.837 1.626c-.246-.835-1.428-.835-1.674 0l-.094.319A1.873 1.873 0 0 1 4.377 3.06l-.292-.16c-.764-.415-1.6.42-1.184 1.185l.159.292a1.873 1.873 0 0 1-1.115 2.692l-.319.094c-.835.246-.835 1.428 0 1.674l.319.094a1.873 1.873 0 0 1 1.115 2.693l-.16.291c-.415.764.42 1.6 1.185 1.184l.292-.159a1.873 1.873 0 0 1 2.692 1.116l.094.318c.246.835 1.428.835 1.674 0l.094-.319a1.873 1.873 0 0 1 2.693-1.115l.291.16c.764.415 1.6-.42 1.184-1.185l-.159-.291a1.873 1.873 0 0 1 1.116-2.693l.318-.094c.835-.246.835-1.428 0-1.674l-.319-.094a1.873 1.873 0 0 1-1.115-2.692l.16-.292c.415-.764-.42-1.6-1.185-1.184l-.291.159A1.873 1.873 0 0 1 8.93 1.945l-.094-.319zm-2.633-.283c.527-1.79 3.065-1.79 3.592 0l.094.319a.873.873 0 0 0 1.255.52l.292-.16c1.64-.892 3.434.901 2.54 2.541l-.159.292a.873.873 0 0 0 .52 1.255l.319.094c1.79.527 1.79 3.065 0 3.592l-.319.094a.873.873 0 0 0-.52 1.255l.16.292c.893 1.64-.902 3.434-2.541 2.54l-.292-.159a.873.873 0 0 0-1.255.52l-.094.319c-.527 1.79-3.065 1.79-3.592 0l-.094-.319a.873.873 0 0 0-1.255-.52l-.292.16c-1.64.893-3.433-.902-2.54-2.541l.159-.292a.873.873 0 0 0-.52-1.255l-.319-.094c-1.79-.527-1.79-3.065 0-3.592l.319-.094a.873.873 0 0 0 .52-1.255l-.16-.292c-.892-1.64.902-3.433 2.541-2.54l.292.159a.873.873 0 0 0 1.255-.52l.094-.319z\"/>\n  <path fill-rule=\"evenodd\" d=\"M8 5.754a2.246 2.246 0 1 0 0 4.492 2.246 2.246 0 0 0 0-4.492zM4.754 8a3.246 3.246 0 1 1 6.492 0 3.246 3.246 0 0 1-6.492 0z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/image.svg":
/*!******************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/image.svg ***!
  \******************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1.0625em\" height=\"1em\" viewBox=\"0 0 17 16\" class=\"bi bi-image\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M14.002 2h-12a1 1 0 0 0-1 1v9l2.646-2.354a.5.5 0 0 1 .63-.062l2.66 1.773 3.71-3.71a.5.5 0 0 1 .577-.094L15.002 9.5V3a1 1 0 0 0-1-1zm-12-1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2h-12zm4 4.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/link-45deg.svg":
/*!***********************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/link-45deg.svg ***!
  \***********************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-link-45deg\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path d=\"M4.715 6.542L3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1.001 1.001 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4.018 4.018 0 0 1-.128-1.287z\"/>\n  <path d=\"M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 0 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 0 0-4.243-4.243L6.586 4.672z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/music-note-list.svg":
/*!****************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/music-note-list.svg ***!
  \****************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-music-note-list\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path d=\"M12 13c0 1.105-1.12 2-2.5 2S7 14.105 7 13s1.12-2 2.5-2 2.5.895 2.5 2z\"/>\n  <path fill-rule=\"evenodd\" d=\"M12 3v10h-1V3h1z\"/>\n  <path d=\"M11 2.82a1 1 0 0 1 .804-.98l3-.6A1 1 0 0 1 16 2.22V4l-5 1V2.82z\"/>\n  <path fill-rule=\"evenodd\" d=\"M0 11.5a.5.5 0 0 1 .5-.5H4a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5zm0-4A.5.5 0 0 1 .5 7H8a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5zm0-4A.5.5 0 0 1 .5 3H8a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/pause-fill.svg":
/*!***********************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/pause-fill.svg ***!
  \***********************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-pause-fill\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path d=\"M5.5 3.5A1.5 1.5 0 0 1 7 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5zm5 0A1.5 1.5 0 0 1 12 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/pause.svg":
/*!******************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/pause.svg ***!
  \******************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-pause\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M6 3.5a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-1 0V4a.5.5 0 0 1 .5-.5zm4 0a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-1 0V4a.5.5 0 0 1 .5-.5z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/play-fill.svg":
/*!**********************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/play-fill.svg ***!
  \**********************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-play-fill\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path d=\"M11.596 8.697l-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/play.svg":
/*!*****************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/play.svg ***!
  \*****************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-play\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M10.804 8L5 4.633v6.734L10.804 8zm.792-.696a.802.802 0 0 1 0 1.392l-6.363 3.692C4.713 12.69 4 12.345 4 11.692V4.308c0-.653.713-.998 1.233-.696l6.363 3.692z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/plus-circle.svg":
/*!************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/plus-circle.svg ***!
  \************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-plus-circle\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z\"/>\n  <path fill-rule=\"evenodd\" d=\"M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/plus.svg":
/*!*****************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/plus.svg ***!
  \*****************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-plus\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/search.svg":
/*!*******************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/search.svg ***!
  \*******************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-search\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M10.442 10.442a1 1 0 0 1 1.415 0l3.85 3.85a1 1 0 0 1-1.414 1.415l-3.85-3.85a1 1 0 0 1 0-1.415z\"/>\n  <path fill-rule=\"evenodd\" d=\"M6.5 12a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zM13 6.5a6.5 6.5 0 1 1-13 0 6.5 6.5 0 0 1 13 0z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/shuffle.svg":
/*!********************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/shuffle.svg ***!
  \********************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-shuffle\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M0 3.5A.5.5 0 0 1 .5 3H1c2.202 0 3.827 1.24 4.874 2.418.49.552.865 1.102 1.126 1.532.26-.43.636-.98 1.126-1.532C9.173 4.24 10.798 3 13 3v1c-1.798 0-3.173 1.01-4.126 2.082A9.624 9.624 0 0 0 7.556 8a9.624 9.624 0 0 0 1.317 1.918C9.828 10.99 11.204 12 13 12v1c-2.202 0-3.827-1.24-4.874-2.418A10.595 10.595 0 0 1 7 9.05c-.26.43-.636.98-1.126 1.532C4.827 11.76 3.202 13 1 13H.5a.5.5 0 0 1 0-1H1c1.798 0 3.173-1.01 4.126-2.082A9.624 9.624 0 0 0 6.444 8a9.624 9.624 0 0 0-1.317-1.918C4.172 5.01 2.796 4 1 4H.5a.5.5 0 0 1-.5-.5z\"/>\n  <path d=\"M13 5.466V1.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384l-2.36 1.966a.25.25 0 0 1-.41-.192zm0 9v-3.932a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384l-2.36 1.966a.25.25 0 0 1-.41-.192z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/skip-end-fill.svg":
/*!**************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/skip-end-fill.svg ***!
  \**************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-skip-end-fill\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M12 3.5a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-1 0V4a.5.5 0 0 1 .5-.5z\"/>\n  <path d=\"M11.596 8.697l-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/skip-end.svg":
/*!*********************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/skip-end.svg ***!
  \*********************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-skip-end\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M12 3.5a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-1 0V4a.5.5 0 0 1 .5-.5z\"/>\n  <path fill-rule=\"evenodd\" d=\"M10.804 8L5 4.633v6.734L10.804 8zm.792-.696a.802.802 0 0 1 0 1.392l-6.363 3.692C4.713 12.69 4 12.345 4 11.692V4.308c0-.653.713-.998 1.233-.696l6.363 3.692z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/skip-start-fill.svg":
/*!****************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/skip-start-fill.svg ***!
  \****************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-skip-start-fill\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M4.5 3.5A.5.5 0 0 0 4 4v8a.5.5 0 0 0 1 0V4a.5.5 0 0 0-.5-.5z\"/>\n  <path d=\"M4.903 8.697l6.364 3.692c.54.313 1.232-.066 1.232-.697V4.308c0-.63-.692-1.01-1.232-.696L4.903 7.304a.802.802 0 0 0 0 1.393z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/skip-start.svg":
/*!***********************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/skip-start.svg ***!
  \***********************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-skip-start\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M4.5 3.5A.5.5 0 0 0 4 4v8a.5.5 0 0 0 1 0V4a.5.5 0 0 0-.5-.5z\"/>\n  <path fill-rule=\"evenodd\" d=\"M5.696 8L11.5 4.633v6.734L5.696 8zm-.792-.696a.802.802 0 0 0 0 1.392l6.363 3.692c.52.302 1.233-.043 1.233-.696V4.308c0-.653-.713-.998-1.233-.696L4.904 7.304z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/soundwave.svg":
/*!**********************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/soundwave.svg ***!
  \**********************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-soundwave\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M8.5 2a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-1 0v-11a.5.5 0 0 1 .5-.5zm-2 2a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5zm4 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5zm-6 1.5A.5.5 0 0 1 5 6v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm8 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm-10 1A.5.5 0 0 1 3 7v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5zm12 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/tools.svg":
/*!******************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/tools.svg ***!
  \******************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-tools\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M0 1l1-1 3.081 2.2a1 1 0 0 1 .419.815v.07a1 1 0 0 0 .293.708L10.5 9.5l.914-.305a1 1 0 0 1 1.023.242l3.356 3.356a1 1 0 0 1 0 1.414l-1.586 1.586a1 1 0 0 1-1.414 0l-3.356-3.356a1 1 0 0 1-.242-1.023L9.5 10.5 3.793 4.793a1 1 0 0 0-.707-.293h-.071a1 1 0 0 1-.814-.419L0 1zm11.354 9.646a.5.5 0 0 0-.708.708l3 3a.5.5 0 0 0 .708-.708l-3-3z\"/>\n  <path fill-rule=\"evenodd\" d=\"M15.898 2.223a3.003 3.003 0 0 1-3.679 3.674L5.878 12.15a3 3 0 1 1-2.027-2.027l6.252-6.341A3 3 0 0 1 13.778.1l-2.142 2.142L12 4l1.757.364 2.141-2.141zm-13.37 9.019L3.001 11l.471.242.529.026.287.445.445.287.026.529L5 13l-.242.471-.026.529-.445.287-.287.445-.529.026L3 15l-.471-.242L2 14.732l-.287-.445L1.268 14l-.026-.529L1 13l.242-.471.026-.529.445-.287.287-.445.529-.026z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/trash.svg":
/*!******************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/trash.svg ***!
  \******************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-trash\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path d=\"M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z\"/>\n  <path fill-rule=\"evenodd\" d=\"M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/volume-mute-fill.svg":
/*!*****************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/volume-mute-fill.svg ***!
  \*****************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-volume-mute-fill\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zm7.137 2.096a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708l4-4a.5.5 0 0 1 .708 0z\"/>\n  <path fill-rule=\"evenodd\" d=\"M9.146 5.646a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/volume-mute.svg":
/*!************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/volume-mute.svg ***!
  \************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-volume-mute\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04zm7.854.606a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708l4-4a.5.5 0 0 1 .708 0z\"/>\n  <path fill-rule=\"evenodd\" d=\"M9.146 5.646a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/volume-up-fill.svg":
/*!***************************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/volume-up-fill.svg ***!
  \***************************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-volume-up-fill\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path d=\"M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z\"/>\n  <path d=\"M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z\"/>\n  <path d=\"M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z\"/>\n  <path fill-rule=\"evenodd\" d=\"M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06z\"/>\n</svg>");

/***/ }),

/***/ "./node_modules/bootstrap-icons/icons/volume-up.svg":
/*!**********************************************************!*\
  !*** ./node_modules/bootstrap-icons/icons/volume-up.svg ***!
  \**********************************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 16 16\" class=\"bi bi-volume-up\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n  <path fill-rule=\"evenodd\" d=\"M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z\"/>\n  <path d=\"M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z\"/>\n  <path d=\"M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z\"/>\n  <path d=\"M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z\"/>\n</svg>");

/***/ }),

/***/ "./src/_.js":
/*!******************!*\
  !*** ./src/_.js ***!
  \******************/
/*! no static exports found */
/***/ (function(module, exports) {

const _ = module.exports;

module.exports.set = function set(object, path, value) {
	const props = path.split('.');
	const lastProp = props.pop();
	const setOn = props.reduce((obj, k) => obj[k] || (obj[k] = {}), object);
	setOn && (setOn[lastProp] = value);
	return object;
};

module.exports.get = function get(object, path, dflt) {
	if (typeof path !== 'string') {
		return dflt;
	}
	const props = path.split('.');
	const lastProp = props.pop();
	const parent = props.reduce((obj, k) => obj && obj[k], object);
	return parent && Object.prototype.hasOwnProperty.call(parent, lastProp)
		? parent[lastProp]
		: dflt;
};

/**
 * Check two values are equal. Arrays/Objects are deep checked.
 */
module.exports.isEqual = function isEqual(a, b, strict = true) {
	if (typeof a !== typeof b) {
		return false;
	}
	if (Array.isArray(a, b)) {
		return a === b || a.length === b.length && a.every((_a, i) => isEqual(_a, b[i]));
	}
	if (a && b && typeof a === 'object' && a !== b) {
		const allKeys = Object.keys(a);
		allKeys.push(...Object.keys(b).filter(k => !allKeys.includes(k)));
		return allKeys.every(key => _.isEqual(a[key], b[key]));
	}
	// eslint-disable-next-line eqeqeq
	return strict ? a === b : a == b;
};

module.exports.toDuration = function toDuration(number) {
	number = Math.floor(number || 0);
	let [ seconds, minutes, hours ] = _duration(0, number);
	seconds < 10 && (seconds = '0' + seconds);
	return (hours ? hours + ':' : '') + minutes + ':' + seconds;
};

module.exports.timeAgo = function timeAgo(date) {
	const [ seconds, minutes, hours, days, weeks ] = _duration(Math.floor(date), Math.floor(Date.now() / 1000));
	/* _eslint-disable indent */
	return weeks > 1 ? weeks + ' weeks ago'
		: days > 0 ? days + (days === 1 ? ' day' : ' days') + ' ago'
		: hours > 0 ? hours + (hours === 1 ? ' hour' : ' hours') + ' ago'
		: minutes > 0 ? minutes + (minutes === 1 ? ' minute' : ' minutes') + ' ago'
		: seconds + (seconds === 1 ? ' second' : ' seconds') + ' ago';
	/* eslint-enable indent */
};

function _duration(from, to) {
	const diff = Math.max(0, to - from);
	return [
		diff % 60,
		Math.floor(diff / 60) % 60,
		Math.floor(diff / 60 / 60) % 24,
		Math.floor(diff / 60 / 60 / 24) % 7,
		Math.floor(diff / 60 / 60 / 24 / 7)
	];
}

module.exports.element = function element(html, parent, events = {}) {
	const container = document.createElement('div');
	container.innerHTML = html;
	const el = container.children[0];
	parent && parent.appendChild(el);
	for (let event in events) {
		el.addEventListener(event, events[event]);
	}
	return el;
};

module.exports.elementBefore = function elementBefore(html, before, events = {}) {
	const el = _.element(html, null, events);
	before.parentNode.insertBefore(el, before);
	return el;
};

module.exports.noDefault = (f, ...args) => e => {
	e.preventDefault();
	const func = typeof f === 'function' ? f : _.get(Player, f);
	func(e, ...args);
};


/***/ }),

/***/ "./src/api.js":
/*!********************!*\
  !*** ./src/api.js ***!
  \********************/
/*! no static exports found */
/***/ (function(module, exports) {

const cache = {};

module.exports = {
	get
};

async function get(url) {
	return new Promise(function (resolve, reject) {
		const headers = {};
		if (cache[url]) {
			headers['If-Modified-Since'] = cache[url].lastModified;
		}
		GM.xmlHttpRequest({
			method: 'GET',
			url,
			headers,
			responseType: 'json',
			onload: response => {
				if (response.status >= 200 && response.status < 300) {
					cache[url] = { lastModified: response.responseHeaders['last-modified'], response: response.response };
				}
				resolve(response.status === 304 ? cache[url].response : response.response);
			},
			onerror: reject
		});
	});
}


/***/ }),

/***/ "./src/components/actions.js":
/*!***********************************!*\
  !*** ./src/components/actions.js ***!
  \***********************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = {
	atRoot: [ 'togglePlay', 'play', 'pause', 'next', 'previous', 'stop', 'toggleMute', 'volumeUp', 'volumeDown' ],
	public: [ 'togglePlay', 'play', 'pause', 'next', 'previous', 'stop', 'toggleMute', 'volumeUp', 'volumeDown' ],

	/**
	 * Switching being playing and paused.
	 */
	togglePlay: function () {
		if (Player.audio.paused) {
			Player.play();
		} else {
			Player.pause();
		}
	},

	/**
	 * Start playback.
	 */
	play: async function (sound, { paused } = {}) {
		try {
			// If nothing is currently selected to play start playing the first sound.
			if (!sound && !Player.playing && Player.sounds.length) {
				sound = Player.sounds[0];
			}

			const video = document.querySelector(`.${ns}-video`);
			video.removeEventListener('canplaythrough', Player.actions._playOnceLoaded);
			Player.audio.removeEventListener('canplaythrough', Player.actions._playOnceLoaded);

			// If a new sound is being played update the display.
			if (sound) {
				if (Player.playing) {
					Player.playing.playing = false;
				}
				// Remove audio events from the video, and add them back for standalone video.
				const audioEvents = Player.controls.audioEvents;
				for (let evt in audioEvents) {
					let handlers = Array.isArray(audioEvents[evt]) ? audioEvents[evt] : [ audioEvents[evt] ];
					handlers.forEach(handler => {
						const handlerFunction = Player.getHandler(handler);
						video.removeEventListener(evt, handlerFunction);
						sound.standaloneVideo && video.addEventListener(evt, handlerFunction);
					});
				}
				sound.playing = true;
				Player.playing = sound;
				Player.audio.src = sound.src;
				Player.isVideo = sound.image.endsWith('.webm') || sound.type === 'video/webm';
				Player.isStandalone = sound.standaloneVideo;
				Player.audio = sound.standaloneVideo ? video : Player.controls._audio;
				await Player.trigger('playsound', sound);
			}

			if (!paused) {
				// If there's a video and sound wait for both to load before playing.
				if (!Player.isStandalone && Player.isVideo && (video.readyState < 3 || Player.audio.readyState < 3)) {
					video.addEventListener('canplaythrough', Player.actions._playOnceLoaded);
					Player.audio.addEventListener('canplaythrough', Player.actions._playOnceLoaded);
				} else {
					Player.audio.play();
				}
			}
		} catch (err) {
			Player.logError('There was an error playing the sound. Please check the console for details.', err);
		}
	},

	/**
	 * Handler to start playback once the video and audio are both loaded.
	 */
	_playOnceLoaded: function () {
		const video = document.querySelector(`.${ns}-video`);
		if (video.readyState > 2 && Player.audio.readyState > 2) {
			video.removeEventListener('canplaythrough', Player.actions._playOnceLoaded);
			Player.audio.removeEventListener('canplaythrough', Player.actions._playOnceLoaded);
			Player.audio.play();
			// Sometimes it just doesn't sync when the playback starts. Give it a second and then force a sync.
			setTimeout(Player.controls.syncVideo, 100);
		}
	},

	/**
	 * Pause playback.
	 */
	pause: function () {
		Player.audio && Player.audio.pause();
	},

	/**
	 * Stop playback.
	 */
	stop: function () {
		Player.audio.src = null;
		Player.playing = null;
		Player.trigger('stop');
	},

	/**
	 * Play the next sound.
	 */
	next: function (opts) {
		Player.actions._movePlaying(1, opts);
	},

	/**
	 * Play the previous sound.
	 */
	previous: function (opts) {
		Player.actions._movePlaying(-1, opts);
	},

	_movePlaying: function (direction, { force, group, paused } = {}) {
		// If there's no sound fall out.
		if (!Player.sounds.length) {
			return;
		}
		// If there's no sound currently playing or it's not in the list then just play the first sound.
		const currentIndex = Player.sounds.indexOf(Player.playing);
		if (currentIndex === -1) {
			return Player.play(Player.sounds[0]);
		}
		// Get the next index, either repeating the same, wrapping round to repeat all or just moving the index.
		let nextSound;
		if (!force && Player.config.repeat === 'one') {
			nextSound = Player.sounds[currentIndex];
		} else {
			let newIndex = currentIndex;
			// Get the next index wrapping round if repeat all is selected
			// Keep going if it's group move, there's still more sounds to check, and the next sound is still in the same group.
			do {
				newIndex = Player.config.repeat === 'all'
					? ((newIndex + direction) + Player.sounds.length) % Player.sounds.length
					: newIndex + direction;
				nextSound = Player.sounds[newIndex];
			} while (group && nextSound && newIndex !== currentIndex && (!nextSound.post || nextSound.post === Player.playing.post));
		}
		nextSound && Player.play(nextSound, { paused });
	},

	/**
	 * Raise the volume by 5%.
	 */
	volumeUp: function () {
		Player.audio.volume = Math.min(Player.audio.volume + 0.05, 1);
	},

	/**
	 * Lower the volume by 5%.
	 */
	volumeDown: function () {
		Player.audio.volume = Math.max(Player.audio.volume - 0.05, 0);
	},

	/**
	 * Mute the audio, or reset it to the last volume prior to muting.
	 */
	toggleMute: function () {
		Player.audio.volume = (Player._lastVolume || 0.5) * !Player.audio.volume;
	}
};


/***/ }),

/***/ "./src/components/colorpicker.js":
/*!***************************************!*\
  !*** ./src/components/colorpicker.js ***!
  \***************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(_) {const HEIGHT = 200;
const WIDTH = 200;

module.exports = {
	delegatedEvents: {
		click: {
			[`.${ns}-colorpicker-input, .${ns}-cp-preview`]: 'colorpicker.create',
			[`.${ns}-apply-colorpicker`]: 'colorpicker._applyColorPicker',
			[`.${ns}-close-colorpicker`]: _.noDefault(() => Player.display.closeDialogs())
		},
		change: {
			[`.${ns}-rgb-input`]: 'colorpicker._handleRGBInput',
		},
		focusout: {
			[`.${ns}-colorpicker-input`]: 'colorpicker._updatePreview'
		},
		mousedown: {
			[`.${ns}-cp-saturation, .${ns}-cp-hue`]: e => {
				const target = e.eventTarget;
				target._mousedown = true;
				document.documentElement.addEventListener('mouseup', _e => delete target._mousedown, { once: true });
				Player.colorpicker[`_handle${e.eventTarget.classList.contains(`${ns}-cp-hue`) ? 'Hue' : 'Saturation'}Move`](e);
			}
		},
		mousemove: {
			[`.${ns}-cp-hue`]: 'colorpicker._handleHueMove',
			[`.${ns}-cp-saturation`]: 'colorpicker._handleSaturationMove'
		}
	},

	initialize: function () {
		Player.on('menu-close', menu => menu._input && (delete menu._input._colorpicker));
	},

	create: function (e) {
		e.preventDefault();
		e.stopPropagation();

		const parent = e.eventTarget.parentNode;
		const input = e.eventTarget.nodeName === 'INPUT' ? e.eventTarget : parent.querySelector('input');
		const preview = parent.querySelector(`.${ns}-cp-preview`);
		if (!input || input._colorpicker) {
			return;
		}

		Player.display.closeDialogs();

		// Get the color from the preview.
		const previewColor = window.getComputedStyle(preview).backgroundColor;
		const rgbMatch = previewColor.match(/rgba?\((\d+), (\d+), (\d+)(?:, ([\d.]+))?\)/);
		const rgb = [ +rgbMatch[1], +rgbMatch[2], +rgbMatch[3], isNaN(+rgbMatch[4]) ? 1 : rgbMatch[4] ];

		const colorpicker = _.element(Player.templates.colorpicker({ HEIGHT, WIDTH, rgb }), parent);
		Player.position.showRelativeTo(colorpicker, input);

		input._colorpicker = colorpicker;
		colorpicker._input = input;

		colorpicker._colorpicker = { hsv: [ 0, 1, 1, 1 ], rgb: rgb };

		// If there's a color in the input then update the hue/saturation positions to show it.
		Player.colorpicker.updateOutput(colorpicker);
	},

	_handleHueMove: function (e) {
		if (!e.eventTarget._mousedown) {
			return;
		}
		e.preventDefault();
		const colorpicker = e.eventTarget.closest(`.${ns}-colorpicker`);
		const y = Math.max(0, e.clientY - e.eventTarget.getBoundingClientRect().top);
		colorpicker._colorpicker.hsv[0] = y / HEIGHT;
		const _hue = Player.colorpicker.hsv2rgb(colorpicker._colorpicker.hsv[0], 1, 1, 1);

		colorpicker.querySelector(`.${ns}-cp-saturation`).style.background = `linear-gradient(to right, white, rgb(${_hue[0]}, ${_hue[1]}, ${_hue[2]}))`;
		e.eventTarget.querySelector('.position').style.top = Math.max(-3, (y - 6)) + 'px';

		Player.colorpicker.updateOutput(colorpicker, true);
	},

	_handleSaturationMove: function (e) {
		if (!e.eventTarget._mousedown) {
			return;
		}
		e.preventDefault();
		const colorpicker = e.eventTarget.closest(`.${ns}-colorpicker`);
		const saturationPosition = e.eventTarget.querySelector('.position');
		const x = Math.max(0, e.clientX - e.eventTarget.getBoundingClientRect().left);
		const y = Math.max(0, e.clientY - e.eventTarget.getBoundingClientRect().top);

		colorpicker._colorpicker.hsv[1] = x / WIDTH;
		colorpicker._colorpicker.hsv[2] = 1 - y / HEIGHT;
		saturationPosition.style.top = Math.min(HEIGHT - 3, Math.max(-3, (y - 6))) + 'px';
		saturationPosition.style.left = Math.min(WIDTH - 3, Math.max(-3, (x - 5))) + 'px';

		Player.colorpicker.updateOutput(colorpicker, true);
	},

	_handleRGBInput: function (e) {
		const colorpicker = e.eventTarget.closest(`.${ns}-colorpicker`);
		colorpicker._colorpicker.rgb[+e.eventTarget.getAttribute('data-color')] = e.eventTarget.value;
		Player.colorpicker.updateOutput(colorpicker);
	},

	updateOutput: function (colorpicker, fromHSV) {
		const order = fromHSV ? [ 'hsv', 'rgb' ] : [ 'rgb', 'hsv' ];
		colorpicker._colorpicker[order[1]] = Player.colorpicker[`${order[0]}2${order[1]}`](...colorpicker._colorpicker[order[0]]);
		const [ r, g, b, a ] = colorpicker._colorpicker.rgb;

		// Update the display.
		if (fromHSV) {
			colorpicker.querySelector(`.${ns}-rgb-input[data-color="0"]`).value = r;
			colorpicker.querySelector(`.${ns}-rgb-input[data-color="1"]`).value = g;
			colorpicker.querySelector(`.${ns}-rgb-input[data-color="2"]`).value = b;
			colorpicker.querySelector(`.${ns}-rgb-input[data-color="3"]`).value = a;
		} else {
			const [ h, s, v ] = colorpicker._colorpicker.hsv;
			const huePos = colorpicker.querySelector(`.${ns}-cp-hue .position`);
			const satPos = colorpicker.querySelector(`.${ns}-cp-saturation .position`);
			const _hue = Player.colorpicker.hsv2rgb(h, 1, 1, 1);
			colorpicker.querySelector(`.${ns}-cp-saturation`).style.background = `linear-gradient(to right, white, rgb(${_hue[0]}, ${_hue[1]}, ${_hue[2]}))`;
			huePos.style.top = (HEIGHT * h) - 3 + 'px';
			satPos.style.left = (s * WIDTH) - 3 + 'px';
			satPos.style.top = ((1 - v) * WIDTH) - 3 + 'px';
		}

		colorpicker.querySelector('.output-color').style.background = `rgb(${r}, ${g}, ${b}, ${a})`;
	},

	_applyColorPicker: function (e) {
		e.preventDefault();
		e.stopPropagation();

		// Update the input.
		const colorpicker = e.eventTarget.closest(`.${ns}-colorpicker`);
		const [ r, g, b, a ] = colorpicker._colorpicker.rgb;
		const input = colorpicker._input;
		input.value = `rgb(${r}, ${g}, ${b}, ${a})`;

		// Remove the colorpicker.
		delete input._colorpicker;
		colorpicker.parentNode.removeChild(colorpicker);

		// Focus and blur to trigger the change handler.
		input.focus();
		input.blur();
	},

	hsv2rgb: function (h, s, v, a) {
		const i = Math.floor((h * 6));
		const f = (h * 6) - i;
		const p = v * (1 - s);
		const q = v * (1 - f * s);
		const t = v * (1 - (1 - f) * s);
		const mod = i % 6;
		const r = [ v, q, p, p, t, v ][mod];
		const g = [ t, v, v, q, p, p ][mod];
		const b = [ p, p, t, v, v, q ][mod];

		return [
			Math.round(r * 255),
			Math.round(g * 255),
			Math.round(b * 255),
			a
		];
	},

	rgb2hsv: function (r, g, b, a) {
		const max = Math.max(r, g, b);
		const min = Math.min(r, g, b);
		const d = max - min;
		const s = (max === 0 ? 0 : d / max);
		const v = max / 255;
		let h;

		/* eslint-disable max-statements-per-line */
		switch (max) {
			case min: h = 0; break;
			case r: h = (g - b) + d * (g < b ? 6 : 0); h /= 6 * d; break;
			case g: h = (b - r) + d * 2; h /= 6 * d; break;
			case b: h = (r - g) + d * 4; h /= 6 * d; break;
		}
		/* eslint-enable max-statements-per-line */

		return [ h, s, v, a ];
	},

	_updatePreview: function (e) {
		const value = e.eventTarget.value;
		const preview = e.eventTarget.parentNode.querySelector(`.${ns}-cp-preview`);
		preview.style.background = value;
	}
};

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/_ */ "./src/_.js")))

/***/ }),

/***/ "./src/components/controls.js":
/*!************************************!*\
  !*** ./src/components/controls.js ***!
  \************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(_) {module.exports = {

	delegatedEvents: {
		click: {
			[`.${ns}-previous-button`]: _.noDefault(() => Player.previous({ force: true })),
			[`.${ns}-play-button`]: _.noDefault('togglePlay'),
			[`.${ns}-next-button`]: _.noDefault(() => Player.next({ force: true })),
			[`.${ns}-seek-bar`]: 'controls.handleSeek',
			[`.${ns}-volume-bar`]: 'controls.handleVolume',
			[`.${ns}-volume-button`]: _.noDefault('toggleMute'),
			[`.${ns}-fullscreen-button`]: 'display.toggleFullScreen'
		},
		mousedown: {
			[`.${ns}-seek-bar`]: () => Player._seekBarDown = true,
			[`.${ns}-volume-bar`]: () => Player._volumeBarDown = true
		},
		mousemove: {
			[`.${ns}-seek-bar`]: e => Player._seekBarDown && Player.controls.handleSeek(e),
			[`.${ns}-volume-bar`]: e => Player._volumeBarDown && Player.controls.handleVolume(e)
		}
	},

	undelegatedEvents: {
		mouseleave: {
			[`.${ns}-seek-bar`]: e => Player._seekBarDown && Player.controls.handleSeek(e),
			[`.${ns}-volume-bar`]: e => Player._volumeBarDown && Player.controls.handleVolume(e)
		},
		mouseup: {
			body: () => {
				Player._seekBarDown = false;
				Player._volumeBarDown = false;
			}
		},
		play: { [`.${ns}-video`]: 'controls.syncVideo' },
		pause: { [`.${ns}-video`]: 'controls.syncVideo' }
	},

	audioEvents: {
		ended: () => Player.next(),
		pause: 'controls.handleAudioEvent',
		play: 'controls.handleAudioEvent',
		seeked: 'controls.handleAudioEvent',
		waiting: 'controls.handleAudioEvent',
		timeupdate: 'controls.updateDuration',
		loadedmetadata: [ 'controls.updateDuration', 'controls.preventWrapping' ],
		durationchange: 'controls.updateDuration',
		volumechange: 'controls.updateVolume',
		loadstart: 'controls.pollForLoading',
		error: 'controls.handleAudioError'
	},

	initialize: async function () {
		// Keep this reference to switch Player.audio to standalone videos and back.
		Player.controls._audio = Player.audio;

		// Apply the previous volume
		GM.getValue('volume').then(volume => volume >= 0 && volume <= 1 && (Player.audio.volume = volume));

		// Only poll for the loaded data when the player is open.
		Player.on('show', () => Player._hiddenWhilePolling && Player.controls.pollForLoading());
		Player.on('hide', () => {
			Player._hiddenWhilePolling = !!Player._loadingPoll;
			Player.controls.stopPollingForLoading();
		});
		Player.on('rendered', () => {
			// Keep track of heavily updated elements.
			Player.ui.currentTimeBar = Player.$(`.${ns}-seek-bar .${ns}-current-bar`);
			Player.ui.loadedBar = Player.$(`.${ns}-seek-bar .${ns}-loaded-bar`);

			// Set the initial volume/seek bar positions and hidden controls.
			Player.controls.updateDuration();
			Player.controls.updateVolume();
			Player.controls.preventWrapping();
		});
		// Show all the controls when wrapping prevention is disabled.
		Player.on('config:preventControlsWrapping', newValue => !newValue && Player.controls.showAllControls());
		// Reset the hidden controls when the hide order is changed.
		Player.on('config:controlsHideOrder', () => {
			Player.controls.setHideOrder();
			Player.controls.preventWrapping();
		});
	},

	/**
	 * Handle audio errors
	 */
	handleAudioError: function (err) {
		if (Player.playing) {
			Player.logError(`Failed to play ${Player.playing.title}. Please check the console for details.`, err, 'warning');
			Player.next();
		}
	},

	/**
	 * Handle audio events. Sync the video up, and update the controls.
	 */
	handleAudioEvent: function () {
		Player.controls.syncVideo();
		Player.controls.updateDuration();
		document.querySelectorAll(`.${ns}-play-button`).forEach(el => {
			el.classList[Player.audio.paused ? 'add' : 'remove'](`${ns}-play`);
		});
	},

	/**
	 * Sync the webm to the audio. Matches the videos time and play state to the audios.
	 */
	syncVideo: function () {
		if (Player.isVideo && !Player.isStandalone) {
			const paused = Player.audio.paused;
			const video = document.querySelector(`.${ns}-video`);
			if (video) {
				video.currentTime = Player.audio.currentTime % video.duration;
				if (paused) {
					video.pause();
				} else {
					video.play();
				}
			}
		}
	},

	/**
	 * Poll for how much has loaded. I know there's the progress event but it unreliable.
	 */
	pollForLoading: function () {
		Player._loadingPoll = Player._loadingPoll || setInterval(Player.controls.updateLoaded, 1000);
	},

	/**
	 * Stop polling for how much has loaded.
	 */
	stopPollingForLoading: function () {
		Player._loadingPoll && clearInterval(Player._loadingPoll);
		Player._loadingPoll = null;
	},

	/**
	 * Update the loading bar.
	 */
	updateLoaded: function () {
		const length = Player.audio.buffered.length;
		const size = length > 0
			? (Player.audio.buffered.end(length - 1) / Player.audio.duration) * 100
			: 0;
		// If it's fully loaded then stop polling.
		size === 100 && Player.controls.stopPollingForLoading();
		Player.ui.loadedBar.style.width = size + '%';
	},

	/**
	 * Update the seek bar and the duration labels.
	 */
	updateDuration: function () {
		const currentTime = _.toDuration(Player.audio.currentTime);
		const duration = _.toDuration(Player.audio.duration);
		document.querySelectorAll(`.${ns}-current-time`).forEach(el => el.innerHTML = currentTime);
		document.querySelectorAll(`.${ns}-duration`).forEach(el => el.innerHTML = duration);
		Player.controls.updateProgressBarPosition(Player.ui.currentTimeBar, Player.audio.currentTime, Player.audio.duration);
	},

	/**
	 * Update the volume bar.
	 */
	updateVolume: function () {
		const vol = Player.audio.volume;
		vol > 0 && (Player._lastVolume = vol);
		GM.setValue('volume', vol);
		document.querySelectorAll(`.${ns}-volume-button`).forEach(el => {
			el.classList[vol === 0 ? 'add' : 'remove']('mute');
			el.classList[vol > 0 ? 'add' : 'remove']('up');
		});
		Player.controls.updateProgressBarPosition(Player.$(`.${ns}-volume-bar .${ns}-current-bar`), Player.audio.volume, 1);
	},

	/**
	 * Update a progress bar width. Adjust the margin of the circle so it's contained within the bar at both ends.
	 */
	updateProgressBarPosition: function (bar, current, total) {
		if (!bar) {
			return;
		}
		current || (current = 0);
		total || (total = 0);
		const ratio = !total ? 0 : Math.max(0, Math.min(((current || 0) / total), 1));
		bar.style.width = `calc(${ratio * 100}% - ${(0.8 * ratio) - 0.4}rem)`;
	},

	/**
	 * Handle the user interacting with the seek bar.
	 */
	handleSeek: function (e) {
		e.preventDefault();
		if (Player.audio.duration && Player.audio.duration !== Infinity) {
			Player.audio.currentTime = Player.audio.duration * Player.controls._getBarXRatio(e);
		}
	},

	/**
	 * Handle the user interacting with the volume bar.
	 */
	handleVolume: function (e) {
		e.preventDefault();
		Player.audio.volume = Player.controls._getBarXRatio(e);
		Player.controls.updateVolume();
	},

	_getBarXRatio: function (e) {
		const offset = 0.4 * Player.remSize;
		return Math.max(0, Math.min(1, (e.offsetX - offset) / (parseInt(getComputedStyle(e.eventTarget || e.target).width, 10) - (2 * offset))));
	},

	/**
	 * Set all controls visible.
	 */
	showAllControls: function () {
		Player.$all(`.${ns}-controls [data-hide-id]`).forEach(el => el.style.display = null);
	},

	/**
	 * Hide elements in the controls instead of wrapping
	 */
	preventWrapping: function () {
		if (!Player.config.preventControlWrapping) {
			return;
		}
		const controls = Player.$(`.${ns}-controls`);
		// If the offset top of the last visible child than this value it indicates wrapping.
		const expectedOffsetTop = parseFloat(window.getComputedStyle(controls).paddingTop);
		const hideElements = Player.controls.hideOrder || Player.controls.setHideOrder();
		let visibleChildren = Array.prototype.slice.call(controls.children);
		let lastChild = visibleChildren.pop();
		let hidden = 0;
		// Show everything to check what has wrapped.
		Player.controls.showAllControls();
		// Keep hiding elements until the last visible child has not wrapped, or there's nothing left to hide.
		while (lastChild.offsetTop > expectedOffsetTop && hidden < hideElements.length) {
			const hide = hideElements[hidden++];
			hide.style.display = 'none';
			visibleChildren = visibleChildren.filter(el => el !== hide);
			hide === lastChild && (lastChild = visibleChildren.pop());
		}
	},

	/**
	 * Set the hide order from the user config.
	 */
	setHideOrder: function () {
		if (!Array.isArray(Player.config.controlsHideOrder)) {
			Player.settings.reset('controlsHideOrder');
		}
		const controls = Player.$(`.${ns}-controls`);
		return Player.controls.hideOrder = Player.config.controlsHideOrder
			.map(id => controls.querySelector(`[data-hide-id="${id}"]`))
			.filter(el => el)
			.sort((a, b) => a.dataset.hideOrder - b.dataset.hideOrder);
	}
};

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/_ */ "./src/_.js")))

/***/ }),

/***/ "./src/components/display.js":
/*!***********************************!*\
  !*** ./src/components/display.js ***!
  \***********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(_) {const selectors = __webpack_require__(/*! ../selectors */ "./src/selectors.js");
const settingsConfig = __webpack_require__(/*! config */ "./src/config/index.js");

const dismissedContentCache = {};
const dismissedRestoreCache = {};

module.exports = {
	atRoot: [ 'show', 'hide' ],
	public: [ 'show', 'hide' ],

	delegatedEvents: {
		click: {
			[`.${ns}-close-button`]: 'hide',
			[`.${ns}-dismiss-link`]: 'display._handleDismiss',
			[`.${ns}-restore-link`]: 'display._handleRestore'
		},
		fullscreenchange: {
			[`.${ns}-media`]: 'display._handleFullScreenChange'
		},
		drop: {
			[`#${ns}-container`]: 'display._handleDrop'
		}
	},

	undelegatedEvents: {
		click: {
			body: 'display.closeDialogs'
		},
		keydown: {
			body: e => e.key === 'Escape' && Player.display.closeDialogs(e)
		}
	},

	initialize: async function () {
		try {
			Player.display.dismissed = (await GM.getValue('dismissed')).split(',');
		} catch (err) {
			Player.display.dismissed = [];
		}
		Player.on('playsound', () => {
			// Reset marquees
			Player.display._marquees = {};
			!Player.display._marqueeTO && Player.display.runTitleMarquee();
		});
		// Store the rem size
		Player.remSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
	},

	/**
	 * Create the player show/hide button in to the 4chan X header.
	 */
	createPlayerButton: function () {
		if (Site === 'FoolFuuka') {
			// Add a sounds link in the nav for archives
			const nav = document.querySelector('.navbar-inner .nav:nth-child(2)');
			const li = _.element('<li><a href="javascript:;">Sounds</a></li>', nav);
			li.children[0].addEventListener('click', Player.display.toggle);
		} else if (Site === 'Fuuka') {
			const br = document.querySelector('body > div > br');
			br.parentNode.insertBefore(document.createTextNode('['), br);
			_.elementBefore('<a href="javascript:;">Sounds</a>', br, { click: Player.display.toggle });
			br.parentNode.insertBefore(document.createTextNode(']'), br);
		} else if (isChanX) {
			// Add a button in the header for 4chan X.
			const showIcon = _.elementBefore(`<span id="shortcut-sounds" class="shortcut brackets-wrap" data-index="0">
				<a href="javascript:;" title="Sounds" class="fa fa-play-circle">Sounds</a>
			</span>`, document.getElementById('shortcut-settings'));
			showIcon.querySelector('a').addEventListener('click', Player.display.toggle);
		} else {
			// Add a [Sounds] link in the top and bottom nav for native 4chan.
			document.querySelectorAll('#settingsWindowLink, #settingsWindowLinkBot').forEach(function (link) {
				_.elementBefore('<a href="javascript:;">Sounds</a>', link, { click: Player.display.toggle });
				link.parentNode.insertBefore(document.createTextNode('] ['), link);
			});
		}
	},

	/**
	 * Render the player.
	 */
	render: async function () {
		try {
			if (Player.container) {
				document.body.removeChild(Player.container);
				document.head.removeChild(Player.stylesheet);
			}

			// Create the main stylesheet.
			Player.display.updateStylesheet();

			// Create the main player. For native threads put it in the threads to get free quote previews.
			const isThread = document.body.classList.contains('is_thread');
			const parent = isThread && !isChanX && document.body.querySelector('.board') || document.body;
			Player.container = _.element(Player.templates.body(), parent);

			Player.trigger('rendered');
		} catch (err) {
			Player.logError('There was an error rendering the sound player.', err);
			// Can't recover, throw.
			throw err;
		}
	},

	forceBoardTheme: function () {
		Player.display.applyBoardTheme(true);
		Player.settings.save();
	},

	applyBoardTheme: function (force) {
		// Create a reply element to gather the style from
		const div = _.element(`<div class="${selectors.styleFetcher}"></div>`, document.body);
		const style = document.defaultView.getComputedStyle(div);

		// Apply the computed style to the color config.
		const colorSettingMap = {
			'colors.text': 'color',
			'colors.background': 'backgroundColor',
			'colors.odd_row': 'backgroundColor',
			'colors.border': 'borderBottomColor',
			// If the border is the same color as the text don't use it as a background color.
			'colors.even_row': style.borderBottomColor === style.color ? 'backgroundColor' : 'borderBottomColor'
		};
		settingsConfig.find(s => s.property === 'colors').settings.forEach(setting => {
			const updateConfig = force || (setting.default === _.get(Player.config, setting.property));
			colorSettingMap[setting.property] && (setting.default = style[colorSettingMap[setting.property]]);
			updateConfig && Player.set(setting.property, setting.default, { bypassSave: true, bypassRender: true, bypassStylesheet: true });
		});

		// Clean up the element.
		document.body.removeChild(div);

		// Updated the stylesheet if it exists.
		Player.stylesheet && Player.display.updateStylesheet();

		// Re-render the settings if needed.
		Player.settings.render();
	},

	updateStylesheet: function () {
		// Add styles to handle 4chan X style not being available.
		if (!isChanX) {
			Player.chanXPFStylesheet = Player.chanXPFStylesheet ||  _.element('<style></style>', document.head);
			Player.chanXPFStylesheet.innerHTML = Player.templates.css4chanXPolyfill();
		}
		// Insert the stylesheet if it doesn't exist.
		Player.stylesheet = Player.stylesheet || _.element('<style></style>', document.head);
		Player.stylesheet.innerHTML = Player.templates.css();
	},

	/**
	 * Change what view is being shown
	 */
	setViewStyle: function (style) {
		// Get the size and style prior to switching.
		const previousStyle = Player.config.viewStyle;
		const { width, height } = Player.container.getBoundingClientRect();

		// Exit fullscreen before changing to a different view.
		if (style !== 'fullscreen') {
			document.fullscreenElement && document.exitFullscreen();
		}

		// Change the style.
		Player.set('viewStyle', style);
		Player.container.setAttribute('data-view-style', style);

		// Try to reapply the pre change sizing unless it was fullscreen.
		if (previousStyle !== 'fullscreen' || style === 'fullscreen') {
			Player.position.resize(parseInt(width, 10), parseInt(height, 10));
		}
		Player.trigger('view', style, previousStyle);
	},

	/**
	 * Togle the display status of the player.
	 */
	toggle: function (e) {
		e && e.preventDefault();
		if (Player.container.style.display === 'none') {
			Player.show();
		} else {
			Player.hide();
		}
	},

	/**
	 * Hide the player. Stops polling for changes, and pauses the aduio if set to.
	 */
	hide: function (e) {
		e && e.preventDefault();
		Player.container.style.display = 'none';

		Player.isHidden = true;
		Player.trigger('hide');
	},

	/**
	 * Show the player. Reapplies the saved position/size, and resumes loaded amount polling if it was paused.
	 */
	show: async function (e) {
		e && e.preventDefault();
		if (!Player.container.style.display) {
			return;
		}
		Player.container.style.display = null;

		Player.isHidden = false;
		await Player.trigger('show');
	},

	/**
	 * Stop playback and close the player.
	 */
	close: async function (e) {
		Player.stop();
		Player.hide();
	},

	/**
	 * Toggle the video/image and controls fullscreen state
	 */
	toggleFullScreen: async function () {
		if (!document.fullscreenElement) {
			// Make sure the player (and fullscreen contents) are visible first.
			if (Player.isHidden) {
				Player.show();
			}
			Player.$(`.${ns}-media`).requestFullscreen();
		} else if (document.exitFullscreen) {
			document.exitFullscreen();
		}
	},

	/**
	 * Handle file/s being dropped on the player.
	 */
	_handleDrop: function (e) {
		e.preventDefault();
		e.stopPropagation();
		Player.playlist.addFromFiles(e.dataTransfer.files);
	},

	/**
	 * Handle the fullscreen state being changed
	 */
	_handleFullScreenChange: function () {
		if (document.fullscreenElement) {
			Player.display.setViewStyle('fullscreen');
			document.querySelector(`.${ns}-image-link`).removeAttribute('href');
		} else {
			if (Player.playing) {
				document.querySelector(`.${ns}-image-link`).href = Player.playing.image;
			}
			Player.playlist.restore();
		}
		Player.controls.preventWrapping();
	},

	_handleRestore: async function (e) {
		e.preventDefault();
		const restore = e.eventTarget.getAttribute('data-restore');
		const restoreIndex = Player.display.dismissed.indexOf(restore);
		if (restore && restoreIndex > -1) {
			Player.display.dismissed.splice(restoreIndex, 1);
			Player.$all(`[data-restore="${restore}"]`).forEach(el => {
				_.elementBefore(dismissedContentCache[restore], el);
				el.parentNode.removeChild(el);
			});
			await GM.setValue('dismissed', Player.display.dismissed.join(','));
		}
	},

	_handleDismiss: async function (e) {
		e.preventDefault();
		const dismiss = e.eventTarget.getAttribute('data-dismiss');
		if (dismiss && !Player.display.dismissed.includes(dismiss)) {
			Player.display.dismissed.push(dismiss);
			Player.$all(`[data-dismiss-id="${dismiss}"]`).forEach(el => {
				_.elementBefore(`<a href="#" class="${ns}-restore-link" data-restore="${dismiss}">${dismissedRestoreCache[dismiss]}</a>`, el);
				el.parentNode.removeChild(el);
			});
			await GM.setValue('dismissed', Player.display.dismissed.join(','));
		}
	},

	ifNotDismissed: function (name, restore, text) {
		dismissedContentCache[name] = text;
		dismissedRestoreCache[name] = restore;
		return Player.display.dismissed.includes(name)
			? `<a href="#" class="${ns}-restore-link" data-restore="${name}">${restore}</a>`
			: text;
	},

	/**
	 * Close any open menus.
	 */
	closeDialogs: function (e) {
		document.querySelectorAll(`.${ns}-menu, .${ns}-colorpicker`).forEach(menu => {
			// Don't close colorpickers when you click inside them.
			if (!e || !menu.classList.contains(`${ns}-colorpicker`) || !menu.contains(e.target)) {
				menu.parentNode.removeChild(menu);
				Player.trigger('menu-close', menu);
			}
		});
	},

	runTitleMarquee: async function () {
		Player.display._marqueeTO = setTimeout(Player.display.runTitleMarquee, 1000);
		document.querySelectorAll(`.${ns}-title-marquee`).forEach(title => {
			const offset = title.parentNode.getBoundingClientRect().width - (title.scrollWidth + 1);
			const location = title.getAttribute('data-location');
			// Fall out if the title is fully visible.
			if (offset >= 0) {
				return title.style.marginLeft = null;
			}
			const data = Player.display._marquees[location] = Player.display._marquees[location] || {
				direction: 1,
				position: parseInt(title.style.marginLeft, 10) || 0
			};
			// Pause at each end.
			if (data.pause > 0) {
				data.pause--;
				return;
			}
			data.position -= (20 * data.direction);

			// Pause then reverse direction when the end is reached.
			if (data.position > 0 || data.position < offset) {
				data.position = Math.min(0, Math.max(data.position, offset));
				data.direction *= -1;
				data.pause = 1;
			}

			title.style.marginLeft = data.position + 'px';
		});
	}
};

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/_ */ "./src/_.js")))

/***/ }),

/***/ "./src/components/events.js":
/*!**********************************!*\
  !*** ./src/components/events.js ***!
  \**********************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = {
	atRoot: [ 'on', 'off', 'trigger' ],

	// Holder of event handlers.
	_events: { },
	_delegatedEvents: { },
	_undelegatedEvents: { },
	_audioEvents: [ ],

	initialize: function () {
		const eventLocations = { Player, ...Player.components };
		const delegated = Player.events._delegatedEvents;
		const undelegated = Player.events._undelegatedEvents;
		const audio = Player.events._audioEvents;

		for (let name in eventLocations) {
			const comp = eventLocations[name];
			for (let evt in comp.delegatedEvents || {}) {
				delegated[evt] || (delegated[evt] = []);
				delegated[evt].push(comp.delegatedEvents[evt]);
			}
			for (let evt in comp.undelegatedEvents || {}) {
				undelegated[evt] || (undelegated[evt] = []);
				undelegated[evt].push(comp.undelegatedEvents[evt]);
			}
			comp.audioEvents && (audio.push(comp.audioEvents));
		}

		Player.on('rendered', function () {
			// Wire up delegated events on the container.
			Player.events.addDelegatedListeners(Player.container, delegated);

			// Wire up undelegated events.
			Player.events.addUndelegatedListeners(document, undelegated);

			// Wire up audio events.
			for (let eventList of audio) {
				for (let evt in eventList) {
					let handlers = Array.isArray(eventList[evt]) ? eventList[evt] : [ eventList[evt] ];
					handlers.forEach(handler => Player.audio.addEventListener(evt, Player.getHandler(handler)));
				}
			}
		});
	},

	/**
	 * Set delegated events listeners on a target
	 */
	addDelegatedListeners(target, events) {
		for (let evt in events) {
			target.addEventListener(evt, function (e) {
				let nodes = [ e.target ];
				while (nodes[nodes.length - 1] !== target) {
					nodes.push(nodes[nodes.length - 1].parentNode);
				}
				for (let node of nodes) {
					for (let eventList of [].concat(events[evt])) {
						for (let selector in eventList) {
							if (node.matches && node.matches(selector)) {
								e.eventTarget = node;
								let handler = Player.getHandler(eventList[selector]);
								// If the handler returns false stop propogation
								if (handler && handler(e) === false) {
									return;
								}
							}
						}
					}
				}
			});
		}
	},

	/**
	 * Set, or reset, directly bound events.
	 */
	addUndelegatedListeners: function (target, events) {
		for (let evt in events) {
			for (let eventList of [].concat(events[evt])) {
				for (let selector in eventList) {
					target.querySelectorAll(selector).forEach(element => {
						const handler = Player.getHandler(eventList[selector]);
						element.removeEventListener(evt, handler);
						element.addEventListener(evt, handler);
					});
				}
			}
		}
	},

	/**
	 * Create an event listener on the player.
	 *
	 * @param {String} evt The name of the events.
	 * @param {function} handler The handler function.
	 */
	on: function (evt, handler) {
		Player.events._events[evt] || (Player.events._events[evt] = []);
		Player.events._events[evt].push(handler);
	},

	/**
	 * Remove an event listener on the player.
	 *
	 * @param {String} evt The name of the events.
	 * @param {function} handler The handler function.
	 */
	off: function (evt, handler) {
		const index = Player.events._events[evt] && Player.events._events[evt].indexOf(handler);
		if (index > -1) {
			Player.events._events[evt].splice(index, 1);
		}
	},

	/**
	 * Trigger an event on the player.
	 *
	 * @param {String} evt The name of the events.
	 * @param {*} data Data passed to the handler.
	 */
	trigger: async function (evt, ...data) {
		const events = Player.events._events[evt] || [];
		for (let handler of events) {
			await handler(...data);
		}
	}
};


/***/ }),

/***/ "./src/components/footer.js":
/*!**********************************!*\
  !*** ./src/components/footer.js ***!
  \**********************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = {
	initialize: function () {
		Player.userTemplate.maintain(Player.footer, 'footerTemplate');
	},

	render: function () {
		if (Player.container) {
			Player.$(`.${ns}-footer`).innerHTML = Player.templates.footer();
		}
	}
};


/***/ }),

/***/ "./src/components/header.js":
/*!**********************************!*\
  !*** ./src/components/header.js ***!
  \**********************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = {
	initialize: function () {
		Player.userTemplate.maintain(Player.header, 'headerTemplate');
	},

	render: function () {
		Player.$(`.${ns}-header`).innerHTML = Player.templates.header();
	}
};


/***/ }),

/***/ "./src/components/hotkeys.js":
/*!***********************************!*\
  !*** ./src/components/hotkeys.js ***!
  \***********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

const settingsConfig = __webpack_require__(/*! config */ "./src/config/index.js");

module.exports = {
	initialize: function () {
		Player.on('rendered', Player.hotkeys.apply);
		Player.on('config:hotkeys', Player.hotkeys.apply);

		// Setup up hardware media keys.
		if ('mediaSession' in navigator && Player.config.hardwareMediaKeys) {
			const actions = [
				[ 'play', () => Player.play() ],
				[ 'pause', () => Player.pause() ],
				[ 'stop', () => Player.pause() ],
				[ 'previoustrack', () => Player.previous() ],
				[ 'nexttrack', () => Player.next() ],
				[ 'seekbackward', evt => Player.audio.currentTime -= evt.seekOffset || 10 ],
				[ 'seekforward', evt => Player.audio.currentTime += evt.seekOffset || 10 ],
				[ 'seekto', evt => Player.audio.currentTime += evt.seekTime ]
			];
			for (let [ type, handler ] of actions) {
				try {
					navigator.mediaSession.setActionHandler(type, handler);
				} catch (err) {
					// not enabled...
				}
			}

			// Keep the media metadata updated.
			Player.audio.addEventListener('pause', () => navigator.mediaSession.playbackState = 'paused');
			Player.audio.addEventListener('ended', () => navigator.mediaSession.playbackState = 'paused');
			Player.audio.addEventListener('play', function () {
				navigator.mediaSession.playbackState = 'playing';
				navigator.mediaSession.metadata = new MediaMetadata({
					title: Player.playing.title,
					artist: '4chan Sounds Player',
					album: document.title,
					artwork: [ { src: Player.playing.thumb } ]
				});
			});
		}
	},

	_keyMap: {
		' ': 'space',
		arrowleft: 'left',
		arrowright: 'right',
		arrowup: 'up',
		arrowdown: 'down'
	},

	addHandler: () => {
		Player.hotkeys.removeHandler();
		document.body.addEventListener('keydown', Player.hotkeys.handle);
	},
	removeHandler: () => {
		document.body.removeEventListener('keydown', Player.hotkeys.handle);
	},

	/**
	 * Apply the selecting hotkeys option
	 */
	apply: function () {
		const type = Player.config.hotkeys;
		Player.hotkeys.removeHandler();
		Player.off('show', Player.hotkeys.addHandler);
		Player.off('hide', Player.hotkeys.removeHandler);

		if (type === 'always') {
			// If hotkeys are always enabled then just set the handler.
			Player.hotkeys.addHandler();
		} else if (type === 'open') {
			// If hotkeys are only enabled with the player toggle the handler as the player opens/closes.
			// If the player is already open set the handler now.
			if (!Player.isHidden) {
				Player.hotkeys.addHandler();
			}
			Player.on('show', Player.hotkeys.addHandler);
			Player.on('hide', Player.hotkeys.removeHandler);
		}
	},

	/**
	 * Handle a keydown even on the body
	 */
	handle: function (e) {
		// Ignore events on inputs so you can still type.
		const ignoreFor = [ 'INPUT', 'SELECT', 'TEXTAREA', 'INPUT' ];
		if (ignoreFor.includes(e.target.nodeName) || Player.isHidden && (Player.config.hotkeys !== 'always' || !Player.sounds.length)) {
			return;
		}
		const k = e.key.toLowerCase();
		const bindings = Player.config.hotkey_bindings || {};

		// Look for a matching hotkey binding
		for (let key in bindings) {
			const keyDef = bindings[key];
			const bindingConfig = k === keyDef.key
				&& (!!keyDef.shiftKey === !!e.shiftKey) && (!!keyDef.ctrlKey === !!e.ctrlKey) && (!!keyDef.metaKey === !!e.metaKey)
				&& (!keyDef.ignoreRepeat || !e.repeat)
				&& settingsConfig.find(s => s.property === 'hotkey_bindings').settings.find(s => s.property === 'hotkey_bindings.' + key);

			if (bindingConfig) {
				e.preventDefault();
				return Player.getHandler(bindingConfig.keyHandler)();
			}
		}
	},

	/**
	 * Turn a hotkey definition or key event into an input string.
	 */
	stringifyKey: function (key) {
		let k = key.key.toLowerCase();
		Player.hotkeys._keyMap[k] && (k = Player.hotkeys._keyMap[k]);
		return (key.ctrlKey ? 'Ctrl+' : '') + (key.shiftKey ? 'Shift+' : '') + (key.metaKey ? 'Meta+' : '') + k;
	},

	/**
	 * Turn an input string into a hotkey definition object.
	 */
	parseKey: function (str) {
		const keys = str.split('+');
		let key = keys.pop();
		Object.keys(Player.hotkeys._keyMap).find(k => Player.hotkeys._keyMap[k] === key && (key = k));
		const newValue = { key };
		keys.forEach(key => newValue[key.toLowerCase() + 'Key'] = true);
		return newValue;
	}
};


/***/ }),

/***/ "./src/components/minimised.js":
/*!*************************************!*\
  !*** ./src/components/minimised.js ***!
  \*************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(_, Icons) {module.exports = {
	_showingPIP: false,

	initialize: function () {
		if (isChanX) {
			Player.userTemplate.maintain(Player.minimised, 'chanXTemplate', [ 'chanXControls' ], [ 'show', 'hide', 'stop' ]);
		}
		Player.on('rendered', Player.minimised.render);
		Player.on('show', Player.minimised.hidePIP);
		Player.on('hide', Player.minimised.showPIP);
		Player.on('stop', Player.minimised.hidePIP);
		Player.on('playsound', Player.minimised.showPIP);
	},

	render: function () {
		if (Player.container && isChanX) {
			let container = document.querySelector(`.${ns}-chan-x-controls`);
			// Create the element if it doesn't exist.
			// Set the user template and control events on it to make all the buttons work.
			if (!container) {
				container = _.elementBefore(`<span class="${ns}-chan-x-controls ${ns}-col-auto ${ns}-align-center"></span>`, document.querySelector('#shortcuts').firstElementChild);
				Player.events.addDelegatedListeners(container, {
					click: [ Player.userTemplate.delegatedEvents.click, Player.controls.delegatedEvents.click ]
				});
			}

			if (Player.config.chanXControls === 'never' || Player.config.chanXControls === 'closed' && !Player.isHidden) {
				return container.innerHTML = '';
			}

			// Render the contents.
			container.innerHTML = Player.userTemplate.build({
				template: Player.config.chanXTemplate,
				location: '4chan-X-controls',
				sound: Player.playing,
				replacements: {
					'prev-button': `<a href="#" class="${ns}-media-control ${ns}-previous-button ${ns}-hover-fill">${Icons.skipStart} ${Icons.skipStartFill}</a>`,
					'play-button': `<a href="#" class="${ns}-media-control ${ns}-play-button ${ns}-hover-fill ${!Player.audio || Player.audio.paused ? `${ns}-play` : ''}">${Icons.play} ${Icons.pause} ${Icons.playFill} ${Icons.pauseFill}</a>`,
					'next-button': `<a href="#" class="${ns}-media-control ${ns}-next-button ${ns}-hover-fill">${Icons.skipEnd} ${Icons.skipEndFill} </a>`,
					'sound-current-time': `<span class="${ns}-current-time">0:00</span>`,
					'sound-duration': `<span class="${ns}-duration">0:00</span>`
				}
			});
		}
	},

	/**
	 * Move the image to a picture in picture like thumnail.
	 */
	showPIP: function () {
		if (!Player.isHidden || !Player.config.pip || !Player.playing || Player.minimised._showingPIP) {
			return;
		}
		Player.minimised._showingPIP = true;
		const image = document.querySelector(`.${ns}-image-link`);
		document.body.appendChild(image);
		image.classList.add(`${ns}-pip`);
		image.style.bottom = (Player.position.getHeaderOffset().bottom + 10) + 'px';
		// Show the player again when the image is clicked.
		image.addEventListener('click', Player.show);
	},

	/**
	 * Move the image back to the player.
	 */
	hidePIP: function () {
		Player.minimised._showingPIP = false;
		const image = document.querySelector(`.${ns}-image-link`);
		Player.$(`.${ns}-media`).insertBefore(document.querySelector(`.${ns}-image-link`), Player.$(`.${ns}-controls`));
		image.classList.remove(`${ns}-pip`);
		image.style.bottom = null;
		image.removeEventListener('click', Player.show);
	}
};

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/_ */ "./src/_.js"), __webpack_require__(/*! ./src/icons */ "./src/icons.js")))

/***/ }),

/***/ "./src/components/playlist.js":
/*!************************************!*\
  !*** ./src/components/playlist.js ***!
  \************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(Icons, _) {const { parseFiles, parseFileName } = __webpack_require__(/*! ../file_parser */ "./src/file_parser.js");
const { postIdPrefix } = __webpack_require__(/*! ../selectors */ "./src/selectors.js");

module.exports = {
	atRoot: [ 'add', 'remove' ],
	public: [ 'search' ],

	delegatedEvents: {
		click: { [`.${ns}-list-item`]: 'playlist.handleSelect' },
		mousemove: { [`.${ns}-list-item`]: 'playlist.positionHoverImage' },
		dragstart: { [`.${ns}-list-item`]: 'playlist.handleDragStart' },
		dragenter: { [`.${ns}-list-item`]: 'playlist.handleDragEnter' },
		dragend: { [`.${ns}-list-item`]: 'playlist.handleDragEnd' },
		dragover: { [`.${ns}-list-item`]: e => e.preventDefault() },
		drop: { [`.${ns}-list-item`]: e => e.preventDefault() },
		keyup: { [`.${ns}-playlist-search`]: 'playlist._handleSearch' },
		contextmenu: { [`.${ns}-list-item`]: 'playlist._handleItemMenu' }
	},

	undelegatedEvents: {
		mouseenter: {
			[`.${ns}-list-item`]: 'playlist.updateHoverImage'
		},
		mouseleave: {
			[`.${ns}-list-item`]: 'playlist.removeHoverImage'
		}
	},

	initialize: function () {
		// Keep track of the last view style so we can return to it.
		Player.playlist._lastView = Player.config.viewStyle === 'playlist' || Player.config.viewStyle === 'image'
			? Player.config.viewStyle
			: 'playlist';

		Player.on('view', style => {
			// Focus the playing song when switching to the playlist.
			style === 'playlist' && Player.playlist.scrollToPlaying();
			// Track state.
			if (style === 'playlist' || style === 'image') {
				Player.playlist._lastView = style;
			}
		});

		// Keey track of  of the hover image element.
		Player.on('rendered', () => Player.playlist.hoverImage = Player.$(`.${ns}-hover-image`));

		// Update the UI when a new sound plays, and scroll to it.
		Player.on('playsound', sound => {
			Player.playlist.showImage(sound);
			Player.$all(`.${ns}-list-item.playing`).forEach(el => el.classList.remove('playing'));
			Player.$(`.${ns}-list-item[data-id="${Player.playing.id}"]`).classList.add('playing');
			Player.playlist.scrollToPlaying('nearest');
			Player.config.autoScrollThread && sound.post && (location.href = location.href.split('#')[0] + '#' + postIdPrefix + sound.post);
		});

		// Reset to the placeholder image when the player is stopped.
		Player.on('stop', () => {
			Player.$all(`.${ns}-list-item.playing`).forEach(el => el.classList.remove('playing'));
			Player.playlist.showImage({ image: `data:image/svg+xml;base64,${btoa(Icons.fcSounds)}` });
		});

		// Reapply filters when they change
		Player.on('config:filters', Player.playlist.applyFilters);

		// Listen to anything that can affect the display of hover images
		Player.on('config:hoverImages', Player.playlist.setHoverImageVisibility);
		Player.on('menu-open', Player.playlist.setHoverImageVisibility);
		Player.on('menu-close', Player.playlist.setHoverImageVisibility);

		// Listen to the search display being toggled
		Player.on('config:showPlaylistSearch', Player.playlist.toggleSearch);

		// Maintain changes to the user templates it's dependent values
		Player.userTemplate.maintain(Player.playlist, 'rowTemplate', [ 'shuffle' ]);
	},

	/**
	 * Render the playlist.
	 */
	render: function () {
		const container = Player.$(`.${ns}-list-container`);
		container.innerHTML = Player.templates.list();
		Player.events.addUndelegatedListeners(document.body, Player.playlist.undelegatedEvents);
		Player.playlist.hoverImage = Player.$(`.${ns}-hover-image`);
	},

	/**
	 * Restore the last playlist or image view.
	 */
	restore: function () {
		Player.display.setViewStyle(Player.playlist._lastView || 'playlist');
	},

	/**
	 * Update the image displayed in the player.
	 */
	showImage: function (sound, thumb) {
		let isVideo = !thumb && (sound.image.endsWith('.webm') || sound.type === 'video/webm');
		const container = document.querySelector(`.${ns}-image-link`);
		const img = container.querySelector(`.${ns}-image`);
		const video = container.querySelector(`.${ns}-video`);
		img.src = '';
		img.src = isVideo || thumb ? sound.thumb : sound.image;
		video.src = isVideo ? sound.image : undefined;
		if (Player.config.viewStyle !== 'fullscreen') {
			container.href = sound.image;
		}
		container.classList[isVideo ? 'add' : 'remove'](ns + '-show-video');
	},

	/**
	 * Switch between playlist and image view.
	 */
	toggleView: function (e) {
		e && e.preventDefault();
		let style = Player.config.viewStyle === 'playlist' ? 'image'
			: Player.config.viewStyle === 'image' ? 'playlist'
			: Player.playlist._lastView;
		Player.display.setViewStyle(style);
	},

	/**
	 * Add a new sound from the thread to the player.
	 */
	add: function (sound, skipRender) {
		try {
			const id = sound.id;
			// Make sure the sound is not a duplicate.
			if (Player.sounds.find(sound => sound.id === id)) {
				return;
			}

			// Add the sound with the location based on the shuffle settings.
			let index = Player.config.shuffle
				? Math.floor(Math.random() * Player.sounds.length - 1)
				: Player.sounds.findIndex(s => Player.compareIds(s.id, id) > 1);
			index < 0 && (index = Player.sounds.length);
			Player.sounds.splice(index, 0, sound);

			if (Player.container) {
				if (!skipRender) {
					// Add the sound to the playlist.
					const list = Player.$(`.${ns}-list-container`);
					let rowContainer = _.element(`<div>${Player.templates.list({ sounds: [ sound ] })}</div>`);
					Player.events.addUndelegatedListeners(rowContainer, Player.playlist.undelegatedEvents);
					if (index < Player.sounds.length - 1) {
						const before = Player.$(`.${ns}-list-item[data-id="${Player.sounds[index + 1].id}"]`);
						list.insertBefore(rowContainer.children[0], before);
					} else {
						list.appendChild(rowContainer.children[0]);
					}
				}

				// If nothing else has been added yet show the image for this sound.
				if (Player.sounds.length === 1) {
					Player.playlist.showImage(sound);
				}
				// Auto show if enabled, we're on a thread, and this is the first non-standlone item.
				if (Player.config.autoshow && /\/thread\//.test(location.href) && Player.sounds.filter(s => !s.standaloneVideo).length === 1) {
					Player.show();
				}
				Player.trigger('add', sound);
			}
		} catch (err) {
			Player.logError('There was an error adding to the sound player. Please check the console for details.', err);
			console.log('[4chan sounds player]', sound);
		}
	},

	addFromFiles: function (files) {
		// Check each of the files for sounds.
		[ ...files ].forEach(file => {
			if (!file.type.startsWith('image') && file.type !== 'video/webm') {
				return;
			}
			const imageSrc = URL.createObjectURL(file);
			const type = file.type;
			let thumbSrc = imageSrc;

			// If it's not a webm just use the full image as the thumbnail
			if (file.type !== 'video/webm') {
				return _continue();
			}

			// If it's a webm grab the first frame as the thumbnail
			const canvas = document.createElement('canvas');
			const video = document.createElement('video');
			const context = canvas.getContext('2d');
			video.addEventListener('loadeddata', function () {
				context.drawImage(video, 0, 0);
				thumbSrc = canvas.toDataURL();
				_continue();
			});
			video.src = imageSrc;

			function _continue() {
				parseFileName(file.name, imageSrc, null, thumbSrc, null, true).forEach(sound => Player.add({ ...sound, local: true, type }));
			}
		});
	},

	/**
	 * Remove a sound
	 */
	remove: function (sound) {
		const index = Player.sounds.indexOf(sound);

		// If the playing sound is being removed then play the next sound.
		if (Player.playing === sound) {
			Player.next({ force: true, paused: Player.audio.paused });
		}
		// Remove the sound from the the list and play order.
		index > -1 && Player.sounds.splice(index, 1);

		// Remove the item from the list.
		Player.$(`.${ns}-list-container`).removeChild(Player.$(`.${ns}-list-item[data-id="${sound.id}"]`));
		Player.trigger('remove', sound);
	},

	/**
	 * Handle an playlist item being clicked. Either open/close the menu or play the sound.
	 */
	handleSelect: function (e) {
		// Ignore if a link was clicked.
		if (e.target.nodeName === 'A' || e.target.closest('a')) {
			return;
		}
		e.preventDefault();
		const id = e.eventTarget.getAttribute('data-id');
		const sound = id && Player.sounds.find(sound => sound.id === id);
		sound && Player.play(sound);
	},

	/**
	 * Read all the sounds from the thread again.
	 */
	refresh: function () {
		parseFiles(document.body);
	},

	/**
	 * Display an item menu.
	 */
	_handleItemMenu: function (e) {
		e.preventDefault();
		e.stopPropagation();
		const id = e.eventTarget.getAttribute('data-id');
		const sound = Player.sounds.find(s => s.id === id);

		// Add row item menus to the list container. Append to the container otherwise.
		const listContainer = e.eventTarget.closest(`.${ns}-list-container`);
		const parent = listContainer || Player.container;

		// Create the menu.
		const dialog = _.element(Player.templates.itemMenu({ sound, postIdPrefix }), parent);
		const relative = e.eventTarget.classList.contains(`${ns}-item-menu-button`) ? e.eventTarget : e;
		Player.userTemplate._showMenu(relative, dialog, parent);
	},

	/**
	 * Toggle the hoverImages setting
	 */
	toggleHoverImages: function (e) {
		e && e.preventDefault();
		Player.set('hoverImages', !Player.config.hoverImages);
	},

	/**
	 * Only show the hover image with the setting enabled, no item menu open, and nothing being dragged.
	 */
	setHoverImageVisibility: function () {
		const container = Player.$(`.${ns}-player`);
		const hideImage = !Player.config.hoverImages
			|| Player.playlist._dragging
			|| container.querySelector(`.${ns}-menu`);
		container.classList[hideImage ? 'add' : 'remove'](`${ns}-hide-hover-image`);
	},

	/**
	 * Set the displayed hover image and reposition.
	 */
	updateHoverImage: function (e) {
		const id = e.currentTarget.getAttribute('data-id');
		const sound = Player.sounds.find(sound => sound.id === id);
		Player.playlist.hoverImage.style.display = 'block';
		Player.playlist.hoverImage.setAttribute('src', sound.thumb);
		Player.playlist.positionHoverImage(e);
	},

	/**
	 * Reposition the hover image to follow the cursor.
	 */
	positionHoverImage: function (e) {
		const { width, height } = Player.playlist.hoverImage.getBoundingClientRect();
		const maxX = document.documentElement.clientWidth - width - 5;
		Player.playlist.hoverImage.style.left = (Math.min(e.clientX, maxX) + 5) + 'px';
		Player.playlist.hoverImage.style.top = (e.clientY - height - 10) + 'px';
	},

	/**
	 * Hide the hover image when nothing is being hovered over.
	 */
	removeHoverImage: function () {
		Player.playlist.hoverImage.style.display = 'none';
	},

	/**
	 * Start dragging a playlist item.
	 */
	handleDragStart: function (e) {
		Player.playlist._dragging = e.eventTarget;
		Player.playlist.setHoverImageVisibility();
		e.eventTarget.classList.add(`${ns}-dragging`);
		e.dataTransfer.setDragImage(new Image(), 0, 0);
		e.dataTransfer.dropEffect = 'move';
		e.dataTransfer.setData('text/plain', e.eventTarget.getAttribute('data-id'));
	},

	/**
	 * Swap a playlist item when it's dragged over another item.
	 */
	handleDragEnter: function (e) {
		if (!Player.playlist._dragging) {
			return;
		}
		e.preventDefault();
		const moving = Player.playlist._dragging;
		const id = moving.getAttribute('data-id');
		let before = e.target.closest && e.target.closest(`.${ns}-list-item`);
		if (!before || moving === before) {
			return;
		}
		const movingIdx = Player.sounds.findIndex(s => s.id === id);
		const list = moving.parentNode;

		// If the item is being moved down it need inserting before the node after the one it's dropped on.
		const position = moving.compareDocumentPosition(before);
		if (position & 0x04) {
			before = before.nextSibling;
		}

		// Move the element and sound.
		// If there's nothing to go before then append.
		if (before) {
			const beforeId = before.getAttribute('data-id');
			const beforeIdx = Player.sounds.findIndex(s => s.id === beforeId);
			const insertIdx = movingIdx < beforeIdx ? beforeIdx - 1 : beforeIdx;
			list.insertBefore(moving, before);
			Player.sounds.splice(insertIdx, 0, Player.sounds.splice(movingIdx, 1)[0]);
		} else {
			Player.sounds.push(Player.sounds.splice(movingIdx, 1)[0]);
			list.appendChild(moving);
		}
		Player.trigger('order');
	},

	/**
	 * Start dragging a playlist item.
	 */
	handleDragEnd: function (e) {
		if (!Player.playlist._dragging) {
			return;
		}
		e.preventDefault();
		delete Player.playlist._dragging;
		e.eventTarget.classList.remove(`${ns}-dragging`);
		Player.playlist.setHoverImageVisibility();
	},

	/**
	 * Scroll to the playing item, unless there is an open menu in the playlist.
	 */
	scrollToPlaying: function (type = 'center') {
		if (Player.$(`.${ns}-list-container .${ns}-menu`)) {
			return;
		}
		const playing = Player.$(`.${ns}-list-item.playing`);
		playing && playing.scrollIntoView({ block: type });
	},

	/**
	 * Remove any user filtered items from the playlist.
	 */
	applyFilters: function () {
		Player.sounds.filter(sound => !Player.acceptedSound(sound)).forEach(Player.playlist.remove);
	},

	/**
	 * Search the playlist
	 */
	_handleSearch: function (e) {
		Player.playlist.search(e.eventTarget.value.toLowerCase());
	},

	search: function (v) {
		const lastSearch = Player.playlist._lastSearch;
		Player.playlist._lastSearch = v;
		if (v === lastSearch) {
			return;
		}
		if (!v) {
			return Player.$all(`.${ns}-list-item`).forEach(el => el.style.display = null);
		}
		Player.sounds.forEach(sound => {
			const row = Player.$(`.${ns}-list-item[data-id="${sound.id}"]`);
			row && (row.style.display = Player.playlist.matchesSearch(sound) ? null : 'none');
		});
	},

	matchesSearch: function (sound) {
		const v = Player.playlist._lastSearch;
		return !v
			|| sound.title.toLowerCase().includes(v)
			|| String(sound.post.toLowerCase()).includes(v)
			|| String(sound.src.toLowerCase()).includes(v);
	},

	toggleSearch: function (show) {
		const input = Player.$(`.${ns}-playlist-search`);
		!show && Player.playlist._lastSearch && Player.playlist.search();
		input.style.display = show ? null : 'none';
		show && input.focus();
	}
};

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/icons */ "./src/icons.js"), __webpack_require__(/*! ./src/_ */ "./src/_.js")))

/***/ }),

/***/ "./src/components/position.js":
/*!************************************!*\
  !*** ./src/components/position.js ***!
  \************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

const selectors = __webpack_require__(/*! ../selectors */ "./src/selectors.js");

module.exports = {
	delegatedEvents: {
		mousedown: {
			[`.${ns}-header`]: 'position.initMove',
			[`.${ns}-expander`]: 'position.initResize'
		}
	},

	initialize: function () {
		// Apply the last position/size, and post width limiting, when the player is shown.
		Player.on('show', async function () {
			const [ top, left ] = (await GM.getValue('position') || '').split(':');
			const [ width, height ] = (await GM.getValue('size') || '').split(':');
			+top && +left && Player.position.move(top, left, true);
			+width && +height && Player.position.resize(width, height);

			if (Player.config.limitPostWidths) {
				Player.position.setPostWidths();
				window.addEventListener('scroll', Player.position.setPostWidths);
			}
		});

		// Remove post width limiting when the player is hidden.
		Player.on('hide', function () {
			Player.position.setPostWidths();
			window.removeEventListener('scroll', Player.position.setPostWidths);
		});

		// Reapply the post width limiting config values when they're changed.
		Player.on('config', prop => {
			if (prop === 'limitPostWidths' || prop === 'minPostWidth') {
				window.removeEventListener('scroll', Player.position.setPostWidths);
				Player.position.setPostWidths();
				if (Player.config.limitPostWidths) {
					window.addEventListener('scroll', Player.position.setPostWidths);
				}
			}
		});

		// Remove post width limit from inline quotes
		new MutationObserver(function () {
			document.querySelectorAll('#hoverUI .postContainer, .inline .postContainer, .backlink_container article').forEach(post => {
				post.style.maxWidth = null;
				post.style.minWidth = null;
			});
		}).observe(document.body, {
			childList: true,
			subtree: true
		});

		// Listen for changes from other tabs
		Player.syncTab('position', value => Player.position.move(...value.split(':').concat(true)));
		Player.syncTab('size', value => Player.position.resize(...value.split(':')));
	},

	/**
	 * Applies a max width to posts next to the player so they don't get hidden behind it.
	 */
	setPostWidths: function () {
		const offset = (document.documentElement.clientWidth - Player.container.offsetLeft) + 10;
		const enabled = !Player.isHidden && Player.config.limitPostWidths;
		const startY = Player.container.offsetTop;
		const endY = Player.container.getBoundingClientRect().height + startY;

		document.querySelectorAll(selectors.limitWidthOf).forEach(post => {
			const rect = enabled && post.getBoundingClientRect();
			const limitWidth = enabled && rect.top + rect.height > startY && rect.top < endY;
			post.style.maxWidth = limitWidth ? `calc(100% - ${offset}px)` : null;
			post.style.minWidth = limitWidth && Player.config.minPostWidth ? `${Player.config.minPostWidth}` : null;
		});
	},

	/**
	 * Handle the user grabbing the expander.
	 */
	initResize: function initDrag(e) {
		e.preventDefault();
		Player._startX = e.clientX;
		Player._startY = e.clientY;
		let { width, height } = Player.container.getBoundingClientRect();
		Player._startWidth = width;
		Player._startHeight = height;
		document.documentElement.addEventListener('mousemove', Player.position.doResize, false);
		document.documentElement.addEventListener('mouseup', Player.position.stopResize, false);
	},

	/**
	 * Handle the user dragging the expander.
	 */
	doResize: function (e) {
		e.preventDefault();
		Player.position.resize(Player._startWidth + e.clientX - Player._startX, Player._startHeight + e.clientY - Player._startY);
	},

	/**
	 * Handle the user releasing the expander.
	 */
	stopResize: function () {
		const { width, height } = Player.container.getBoundingClientRect();
		document.documentElement.removeEventListener('mousemove', Player.position.doResize, false);
		document.documentElement.removeEventListener('mouseup', Player.position.stopResize, false);
		GM.setValue('size', width + ':' + height);
	},

	/**
	 * Resize the player.
	 */
	resize: function (width, height) {
		if (!Player.container || Player.config.viewStyle === 'fullscreen') {
			return;
		}
		const { bottom } = Player.position.getHeaderOffset();
		// Make sure the player isn't going off screen.
		height = Math.min(height, document.documentElement.clientHeight - Player.container.offsetTop - bottom);
		width = Math.min(width - 2, document.documentElement.clientWidth - Player.container.offsetLeft);

		Player.container.style.width = width + 'px';

		// Which element to change the height of depends on the view being displayed.
		const heightElement = Player.config.viewStyle === 'playlist' ? Player.$(`.${ns}-list-container`)
			: Player.config.viewStyle === 'image' ? Player.$(`.${ns}-image-link`)
			: Player.config.viewStyle === 'settings' ? Player.$(`.${ns}-settings`)
			: Player.config.viewStyle === 'threads' ? Player.$(`.${ns}-threads`)
			: Player.config.viewStyle === 'tools' ? Player.$(`.${ns}-tools`) : null;

		if (!heightElement) {
			return;
		}

		const offset = Player.container.getBoundingClientRect().height - heightElement.getBoundingClientRect().height;
		heightElement.style.height = (height - offset) + 'px';

		Player.controls.preventWrapping();
	},

	/**
	 * Handle the user grabbing the header.
	 */
	initMove: function (e) {
		e.preventDefault();
		Player.$(`.${ns}-header`).style.cursor = 'grabbing';

		// Try to reapply the current sizing to fix oversized winows.
		const { width, height } = Player.container.getBoundingClientRect();
		Player.position.resize(width, height);

		Player._offsetX = e.clientX - Player.container.offsetLeft;
		Player._offsetY = e.clientY - Player.container.offsetTop;
		document.documentElement.addEventListener('mousemove', Player.position.doMove, false);
		document.documentElement.addEventListener('mouseup', Player.position.stopMove, false);
	},

	/**
	 * Handle the user dragging the header.
	 */
	doMove: function (e) {
		e.preventDefault();
		Player.position.move(e.clientX - Player._offsetX, e.clientY - Player._offsetY);
	},

	/**
	 * Handle the user releasing the heaer.
	 */
	stopMove: function () {
		document.documentElement.removeEventListener('mousemove', Player.position.doMove, false);
		document.documentElement.removeEventListener('mouseup', Player.position.stopMove, false);
		Player.$(`.${ns}-header`).style.cursor = null;
		GM.setValue('position', parseInt(Player.container.style.left, 10) + ':' + parseInt(Player.container.style.top, 10));
	},

	/**
	 * Move the player.
	 */
	move: function (x, y, allowOffscreen) {
		if (!Player.container) {
			return;
		}

		const { top, bottom } = Player.position.getHeaderOffset();

		// Ensure the player stays fully within the window.
		const { width, height } = Player.container.getBoundingClientRect();
		const maxX = allowOffscreen ? Infinity : document.documentElement.clientWidth - width;
		const maxY = allowOffscreen ? Infinity : document.documentElement.clientHeight - height - bottom;

		// Move the window.
		Player.container.style.left = Math.max(0, Math.min(x, maxX)) + 'px';
		Player.container.style.top = Math.max(top, Math.min(y, maxY)) + 'px';

		if (Player.config.limitPostWidths) {
			Player.position.setPostWidths();
		}
	},

	/**
	 * Get the offset from the top or bottom required for the 4chan X header.
	 */
	getHeaderOffset: function () {
		const docClasses = document.documentElement.classList;
		const hasChanXHeader = docClasses.contains('fixed');
		const headerHeight = hasChanXHeader ? document.querySelector('#header-bar').getBoundingClientRect().height : 0;
		const top = hasChanXHeader && docClasses.contains('top-header') ? headerHeight : 0;
		const bottom = hasChanXHeader && docClasses.contains('bottom-header') ? headerHeight : 0;

		return { top, bottom };
	},

	/**
	 * Position a fixed item with respect to an element or event.
	 */
	showRelativeTo: function (item, relative) {
		// Try and put the item aligned to the left under the relative.
		const relRect = relative instanceof Node
			? relative.getBoundingClientRect()
			: { top: relative.clientY, left: relative.clientX, width: 0, height: 0 };
		item.style.top = relRect.top + relRect.height + 'px';
		item.style.left = relRect.left + 'px';

		// Reposition around the relative if the item is off screen.
		const { width: width, height: height } = item.getBoundingClientRect();
		if (relRect.left + width > document.documentElement.clientWidth) {
			item.style.left = (relRect.left + relRect.width - width) + 'px';
		}
		if (relRect.top + relRect.height + height > document.documentElement.clientHeight - Player.position.getHeaderOffset().bottom) {
			item.style.top = (relRect.top - height) + 'px';
		}
	}
};


/***/ }),

/***/ "./src/components/settings.js":
/*!************************************!*\
  !*** ./src/components/settings.js ***!
  \************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(_) {const settingsConfig = __webpack_require__(/*! config */ "./src/config/index.js");
const migrations = __webpack_require__(/*! ../migrations */ "./src/migrations.js");

module.exports = {
	atRoot: [ 'set' ],

	changelog: 'https://github.com/rcc11/4chan-sounds-player/releases',

	delegatedEvents: {
		click: {
			[`.${ns}-settings .${ns}-heading-action`]: 'settings._handleAction',
			[`.${ns}-settings-tab`]: 'settings._handleTab'
		},
		focusout: {
			[`.${ns}-settings input, .${ns}-settings textarea`]: 'settings._handleChange'
		},
		change: {
			[`.${ns}-settings input[type=checkbox], .${ns}-settings select`]: 'settings._handleChange'
		},
		keydown: {
			[`.${ns}-key-input`]: 'settings.handleKeyChange',
		}
	},

	initialize: async function () {
		Player.settings.view = 'Display';

		// Apply the default board theme as default.
		Player.display.applyBoardTheme();

		// Apply the default config.
		Player.config = settingsConfig.reduce(function reduceSettings(config, setting) {
			if (setting.settings) {
				setting.settings.forEach(subSetting => {
					let _setting = { ...setting, ...subSetting };
					_.set(config, _setting.property, _setting.default);
				});
				return config;
			}
			return _.set(config, setting.property, setting.default);
		}, {});

		// Load the user config.
		await Player.settings.load();

		if (Player.config.showUpdatedNotification && Player.config.VERSION && Player.config.VERSION !== "3.2.1") {
			Player.alert(`4chan Sounds Player has been updated to <a href="${Player.settings.changelog}" target="_blank">version ${"3.2.1"}</a>.`);
		}

		// Run any migrations.
		await Player.settings.migrate(Player.config.VERSION);

		// Listen for the player closing to apply the pause on hide setting.
		Player.on('hide', function () {
			if (Player.config.pauseOnHide) {
				Player.pause();
			}
		});

		// Listen for changes from other tabs
		Player.syncTab('settings', value => Player.settings.apply(value, {
			bypassSave: true,
			applyDefault: true,
			ignore: [ 'viewStyle' ]
		}));
	},

	render: function () {
		if (Player.container) {
			Player.$(`.${ns}-settings`).innerHTML = Player.templates.settings();
		}
	},

	/**
	 * Update a setting.
	 */
	set: function (property, value, { bypassValidation, bypassSave, bypassRender, silent, bypassStylesheet, settingConfig } = {}) {
		settingConfig = settingConfig || Player.settings.findDefault(property);
		const previousValue = _.get(Player.config, property);

		// Check if the value has actually changed.
		if (!bypassValidation && _.isEqual(previousValue, value)) {
			return;
		}

		// Set the new value.
		_.set(Player.config, property, value);

		// Trigger events, unless they are disabled in opts.
		!bypassStylesheet && settingConfig && settingConfig.updateStylesheet && Player.display.updateStylesheet();
		!silent && Player.trigger('config', property, value, previousValue);
		!silent && Player.trigger('config:' + property, value, previousValue);
		!bypassSave && Player.settings.save();
		!bypassRender && settingConfig.displayGroup && Player.settings.render();
	},

	/**
	 * Reset a setting to the default value
	 */
	reset: function (property) {
		let settingConfig = Player.settings.findDefault(property);
		Player.set(property, settingConfig.default, { settingConfig });
	},

	/**
	 * Persist the player settings.
	 */
	save: function () {
		try {
			// Filter settings that haven't been modified from the default.
			const settings = settingsConfig.reduce(function _handleSetting(settings, setting) {
				if (setting.settings) {
					setting.settings.forEach(subSetting => _handleSetting(settings, {
						property: setting.property,
						default: setting.default,
						...subSetting
					}));
				} else {
					let userVal = _.get(Player.config, setting.property);
					if (userVal !== undefined && !_.isEqual(userVal, setting.default)) {
						// If the setting is a mixed in object only store items that differ from the default.
						if (setting.mix) {
							userVal = Object.keys(userVal).reduce((changed, key) => {
								if (!_.isEqual(setting.default[key], userVal[key])) {
									changed[key] = userVal[key];
								}
								return changed;
							}, {});
						}
						_.set(settings, setting.property, userVal);
					}
				}
				return settings;
			}, {});
			// Show the playlist or image view on load, whichever was last shown.
			settings.viewStyle = Player.playlist._lastView;
			// Store the player version with the settings.
			settings.VERSION = "3.2.1";
			// Save the settings.
			return GM.setValue('settings', JSON.stringify(settings));
		} catch (err) {
			Player.logError('There was an error saving the sound player settings.', err);
		}
	},

	/**
	 * Restore the saved player settings.
	 */
	load: async function () {
		try {
			let settings = await GM.getValue('settings') || await GM.getValue(ns + '.settings');
			if (settings) {
				Player.settings.apply(settings, { bypassSave: true, silent: true });
			}
		} catch (err) {
			Player.logError('There was an error loading the sound player settings.', err);
		}
	},

	apply: function (settings, opts = {}) {
		if (typeof settings === 'string') {
			settings = JSON.parse(settings);
		}
		settings.VERSION && (Player.config.VERSION = settings.VERSION);
		settingsConfig.forEach(function _handleSetting(setting) {
			if (setting.settings) {
				return setting.settings.forEach(subSetting => _handleSetting({
					property: setting.property,
					default: setting.default,
					...subSetting
				}));
			}
			if (opts.ignore && opts.ignore.includes(setting.property)) {
				return;
			}
			let value = _.get(settings, setting.property, opts.applyDefault ? setting.default : undefined);
			if (value !== undefined) {
				if (setting.mix) {
					// Mix in default.
					value = { ...setting.default, ...(value || {}) };
				}
				Player.set(setting.property, value, { ...opts, settingConfig: setting });
			}
		});
	},

	/**
	 * Run migrations when the player is updated.
	 */
	migrate: async function (fromVersion) {
		// Fall out if the player hasn't updated.
		if (!fromVersion || fromVersion === "3.2.1") {
			return;
		}
		for (let i = 0; i < migrations.length; i++) {
			let mig = migrations[i];
			if (Player.settings.compareVersions(fromVersion, mig.version) < 0) {
				try {
					console.log('[4chan sound player] Migrate:', mig.name);
					await mig.run();
				} catch (err) {
					console.error(err);
				}
			}
		}
		Player.settings.save();
	},

	/**
	 * Compare two semver strings.
	 */
	compareVersions: function (a, b) {
		const [ aVer, aHash ] = a.split('-');
		const [ bVer, bHash ] = b.split('-');
		const aParts = aVer.split('.');
		const bParts = bVer.split('.');
		for (let i = 0; i < 3; i++) {
			if (+aParts[i] > +bParts[i]) {
				return 1;
			}
			if (+aParts[i] < +bParts[i]) {
				return -1;
			}
		}
		return aHash !== bHash;
	},

	/**
	 * Find a setting in the default configuration.
	 */
	findDefault: function (property) {
		let settingConfig;
		settingsConfig.find(function (setting) {
			if (setting.property === property) {
				return settingConfig = setting;
			}
			if (setting.settings) {
				let subSetting = setting.settings.find(_setting => _setting.property === property);
				return subSetting && (settingConfig = { ...setting, settings: null, ...subSetting });
			}
			return false;
		});
		return settingConfig || { property };
	},

	/**
	 * Toggle whether the player or settings are displayed.
	 */
	toggle: function (group) {
		// Blur anything focused so the change is applied.
		let focused = Player.$(`.${ns}-settings :focus`);
		focused && focused.blur();

		// Restore the playlist if there's no group given and the settings are already open.
		if (!group && Player.config.viewStyle === 'settings') {
			return Player.playlist.restore();
		}
		// Switch to the settings view if it's not already showing.
		if (Player.config.viewStyle !== 'settings') {
			Player.display.setViewStyle('settings');
		}
		// Switch to a given group.
		if (group && group !== Player.settings.view) {
			Player.settings.showGroup(group);
		}
	},

	/**
	 * Switch the displayed group
	 */
	_handleTab: function (e) {
		const group = e.eventTarget.getAttribute('data-group');
		if (group) {
			e.preventDefault();
			Player.settings.showGroup(group);
		}
	},

	showGroup: function (group) {
		Player.settings.view = group;
		const currentGroup = Player.$(`.${ns}-settings-group.active`);
		const currentTab = Player.$(`.${ns}-settings-tab.active`);
		currentGroup && currentGroup.classList.remove('active');
		currentTab && currentTab.classList.remove('active');
		Player.$(`.${ns}-settings-group[data-group="${group}"]`).classList.add('active');
		Player.$(`.${ns}-settings-tab[data-group="${group}"]`).classList.add('active');
	},

	/**
	 * Handle the user making a change in the settings view.
	 */
	_handleChange: function (e) {
		try {
			const input = e.eventTarget;
			const property = input.getAttribute('data-property');
			if (!property) {
				return;
			}
			let settingConfig = Player.settings.findDefault(property);

			// Get the new value of the setting.
			const currentValue = _.get(Player.config, property);
			let newValue = input[input.getAttribute('type') === 'checkbox' ? 'checked' : 'value'];

			if (settingConfig.parse) {
				newValue = Player.getHandler(settingConfig.parse)(newValue, currentValue, e);
			}
			if (settingConfig && settingConfig.split) {
				newValue = newValue.split(decodeURIComponent(settingConfig.split));
			}

			// Not the most stringent check but enough to avoid some spamming.
			if (!_.isEqual(currentValue, newValue, !settingConfig.looseCompare)) {
				// Update the setting.
				Player.set(property, newValue, { bypassValidation: true, bypassRender: true, settingConfig });
			}
		} catch (err) {
			Player.logError('There was an error updating the setting.', err);
		}
	},

	/**
	 * Converts a key event in an input to a string representation set as the input value.
	 */
	handleKeyChange: function (e) {
		e.preventDefault();
		if (e.key === 'Shift' || e.key === 'Control' || e.key === 'Meta') {
			return;
		}
		e.eventTarget.value = Player.hotkeys.stringifyKey(e);
	},

	/**
	 * Handle an action link next to a heading being clicked.
	 */
	_handleAction: function (e) {
		e.preventDefault();
		const property = e.eventTarget.getAttribute('data-property');
		const handlerName = e.eventTarget.getAttribute('data-handler');
		const handler = _.get(Player, handlerName);
		handler && handler(property, e);
	},

	renderHosts: function (_value) {
		return `<div class="${ns}-host-inputs">`
			+ Object.keys(Player.config.uploadHosts).map(Player.templates.hostInput).join('')
		+ '</div>';
	},

	parseHosts: function (newValue, hosts, e) {
		hosts = { ...hosts };
		const container = e.eventTarget.closest(`.${ns}-host-input`);
		let name = container.getAttribute('data-host-name');
		let host = hosts[name] = { ...hosts[name] };
		const changedField = e.eventTarget.getAttribute('name');

		try {
			// If the name was changed then reassign in hosts and update the data-host-name attribute.
			if (changedField === 'name' && newValue !== name) {
				if (!newValue || hosts[newValue]) {
					throw new PlayerError('A unique name for the host is required.', 'warning');
				}
				container.setAttribute('data-host-name', newValue);
				hosts[newValue] = host;
				delete hosts[name];
				name = newValue;
			}

			// Validate URL
			if (changedField === 'url' || changedField === 'soundUrl') {
				try {
					(changedField === 'url' || newValue) && new URL(newValue);
				} catch (err) {
					throw new PlayerError('The value must be a valid URL.', 'warning');
				}
			}

			// Parse the data
			if (changedField === 'data') {
				try {
					newValue = JSON.parse(newValue);
				} catch (err) {
					throw new PlayerError('The data must be valid JSON.', 'warning');
				}
			}

			if (changedField === 'headers') {
				try {
					newValue = newValue ? JSON.parse(newValue) : undefined;
				} catch (err) {
					throw new PlayerError('The headers must be valid JSON.', 'warning');
				}
			}
		} catch (err) {
			host.invalid = true;
			container.classList.add('invalid');
			throw err;
		}

		if (newValue === undefined) {
			delete host[changedField];
		} else {
			host[changedField] = newValue;
		}

		try {
			const soundUrlValue = container.querySelector('[name=soundUrl]').value;
			const headersValue = container.querySelector('[name=headers]').value;
			if (name
				&& JSON.parse(container.querySelector('[name=data]').value)
				&& new URL(container.querySelector('[name=url]').value)
				&& (!soundUrlValue || new URL(soundUrlValue))
				&& (!headersValue || JSON.parse(headersValue))) {

				delete host.invalid;
				container.classList.remove('invalid');
			}
		} catch (err) {
			// leave it invalid
		}

		return hosts;
	},

	addUploadHost: function () {
		const hosts = Player.config.uploadHosts;
		const container = Player.$(`.${ns}-host-inputs`);
		let name = 'New Host';
		let i = 1;
		while (Player.config.uploadHosts[name]) {
			name = name + ' ' + ++i;
		}
		hosts[name] = { invalid: true, data: { file: '$file' } };
		if (container.children[0]) {
			_.elementBefore(Player.templates.hostInput(name), container.children[0]);
		} else {
			_.element(Player.templates.hostInput(name), container);
		}
		Player.settings.set('uploadHosts', hosts, { bypassValidation: true, bypassRender: true, silent: true });
	},

	removeHost: function (prop, e) {
		const hosts = Player.config.uploadHosts;
		const container = e.eventTarget.closest(`.${ns}-host-input`);
		const name = container.getAttribute('data-host-name');
		// For hosts in the defaults set null so we know to not include them on load
		if (Player.settings.findDefault('uploadHosts').default[name]) {
			hosts[name] = null;
		} else {
			delete hosts[name];
		}
		container.parentNode.removeChild(container);
		Player.settings.set('uploadHosts', hosts, { bypassValidation: true, bypassRender: true });
	},

	setDefaultHost: function (_new, _current, e) {
		const selected = e.eventTarget.closest(`.${ns}-host-input`).getAttribute('data-host-name');
		if (selected === Player.config.defaultUploadHost) {
			return selected;
		}

		Object.keys(Player.config.uploadHosts).forEach(name => {
			const checkbox = Player.$(`.${ns}-host-input[data-host-name="${name}"] input[data-property="defaultUploadHost"]`);
			checkbox && (checkbox.checked = name === selected);
		});
		return selected;
	},

	restoreDefaultHosts: function () {
		Object.assign(Player.config.uploadHosts, Player.settings.findDefault('uploadHosts').default);
		Player.set('uploadHosts', Player.config.uploadHosts, { bypassValidation: true });
	}
};

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/_ */ "./src/_.js")))

/***/ }),

/***/ "./src/components/threads.js":
/*!***********************************!*\
  !*** ./src/components/threads.js ***!
  \***********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(_) {const { parseFileName } = __webpack_require__(/*! ../file_parser */ "./src/file_parser.js");
const { get } = __webpack_require__(/*! ../api */ "./src/api.js");

const maxSavedBoards = 10;
const boardsURL = 'https://a.4cdn.org/boards.json';
const catalogURL = 'https://a.4cdn.org/%s/catalog.json';

module.exports = {
	boardList: null,
	soundThreads: null,
	displayThreads: {},
	selectedBoards: Board ? [ Board ] : [ 'a' ],
	showAllBoards: false,

	delegatedEvents: {
		click: {
			[`.${ns}-fetch-threads-link`]: 'threads.fetch',
			[`.${ns}-all-boards-link`]: 'threads.toggleBoardList',
			[`.${ns}-threads-view-style`]: 'threads.toggleView'
		},
		keyup: {
			[`.${ns}-threads-filter`]: e => Player.threads.filter(e.eventTarget.value)
		},
		change: {
			[`.${ns}-threads input[type=checkbox]`]: 'threads.toggleBoard'
		}
	},

	initialize: async function () {
		Player.threads.hasParser = is4chan && typeof Parser !== 'undefined';
		// If the native Parser hasn't been intialised chuck customSpoiler on it so we can call it for threads.
		// You shouldn't do things like this. We can fall back to the table view if it breaks though.
		if (Player.threads.hasParser && !Parser.customSpoiler) {
			Parser.customSpoiler = {};
		}

		Player.on('show', Player.threads._initialFetch);
		Player.on('view', Player.threads._initialFetch);
		Player.on('rendered', Player.threads.afterRender);
		Player.on('config:threadsViewStyle', Player.threads.render);
		try {
			const savedBoards = await GM.getValue('threads_board_selection');
			savedBoards && (Player.threads.selectedBoards = savedBoards.split(','));
		} catch (err) {
			// Leave it defaulted to the current board.
		}
	},

	/**
	 * Fetch the threads when the threads view is opened for the first time.
	 */
	_initialFetch: function () {
		if (Player.container && Player.config.viewStyle === 'threads' && Player.threads.boardList === null) {
			Player.threads.fetchBoards(true);
		}
	},

	render: function () {
		if (Player.container) {
			Player.$(`.${ns}-threads`).innerHTML = Player.templates.threads();
			Player.threads.afterRender();
		}
	},

	/**
	 * Render the threads and apply the board styling after the view is rendered.
	 */
	afterRender: function () {
		const threadList = Player.$(`.${ns}-thread-list`);
		if (threadList) {
			const bodyStyle = document.defaultView.getComputedStyle(document.body);
			threadList.style.background = bodyStyle.backgroundColor;
			threadList.style.backgroundImage = bodyStyle.backgroundImage;
			threadList.style.backgroundRepeat = bodyStyle.backgroundRepeat;
			threadList.style.backgroundPosition = bodyStyle.backgroundPosition;
		}
		Player.threads.renderThreads();
	},

	/**
	 * Switch between board and table view.
	 */
	toggleView: function (e) {
		e.preventDefault();
		Player.set('threadsViewStyle', e.eventTarget.getAttribute('data-style'));
	},

	/**
	 * Render just the threads.
	 */
	renderThreads: function () {
		if (!Player.threads.hasParser || Player.config.threadsViewStyle === 'table') {
			Player.$(`.${ns}-threads-body`).innerHTML = Player.templates.threadList();
		} else {
			try {
				const list = Player.$(`.${ns}-thread-list`);
				list.innerHTML = '';
				for (let board in Player.threads.displayThreads) {
					// Create a board title
					const boardConf = Player.threads.boardList.find(boardConf => boardConf.board === board);
					const boardTitle = `/${boardConf.board}/ - ${boardConf.title}`;
					_.element(`<div class="boardBanner"><div class="boardTitle">${boardTitle}</div></div>`, list);

					// Add each thread for the board
					const threads = Player.threads.displayThreads[board];
					for (let i = 0; i < threads.length; i++) {
						list.appendChild(Parser.buildHTMLFromJSON.call(Parser, threads[i], threads[i].board, true, true));

						// Add a line under each thread
						_.element('<hr style="clear: both">', list);
					}
				}
			} catch (err) {
				Player.logError('Unable to display the threads board view.', err, 'warning');
				// If there was an error fall back to the table view.
				Player.set('threadsViewStyle', 'table');
				Player.renderThreads();
			}
		}
	},

	/**
	 * Render just the board selection.
	 */
	renderBoards: function () {
		Player.$(`.${ns}-thread-board-list`).innerHTML = Player.templates.threadBoards();
	},

	/**
	 * Toggle the threads view.
	 */
	toggle: function (e) {
		e && e.preventDefault();
		if (Player.config.viewStyle === 'threads') {
			Player.playlist.restore();
		} else {
			Player.display.setViewStyle('threads');
		}
	},

	/**
	 * Switch between showing just the selected boards and all boards.
	 */
	toggleBoardList: function (e) {
		e.preventDefault();
		Player.threads.showAllBoards = !Player.threads.showAllBoards;
		Player.$(`.${ns}-all-boards-link`).innerHTML = Player.threads.showAllBoards ? 'Selected Only' : 'Show All';
		Player.threads.renderBoards();
	},

	/**
	 * Select/deselect a board.
	 */
	toggleBoard: async function (e) {
		const board = e.eventTarget.value;
		const selected = e.eventTarget.checked;
		if (selected) {
			!Player.threads.selectedBoards.includes(board) && Player.threads.selectedBoards.unshift(board);
		} else {
			Player.threads.selectedBoards = Player.threads.selectedBoards.filter(b => b !== board);
		}
		await GM.setValue('threads_board_selection', Player.threads.selectedBoards.slice(0, maxSavedBoards).join(','));
	},

	/**
	 * Fetch the board list from the 4chan API.
	 */
	fetchBoards: async function (fetchThreads) {
		Player.threads.loading = true;
		Player.threads.render();
		Player.threads.boardList = (await get(boardsURL)).boards;
		if (fetchThreads) {
			Player.threads.fetch();
		} else {
			Player.threads.loading = false;
			Player.threads.render();
		}
	},

	/**
	 * Fetch the catalog for each selected board and search for sounds in OPs.
	 */
	fetch: async function (e) {
		e && e.preventDefault();
		Player.threads.loading = true;
		Player.threads.render();
		if (!Player.threads.boardList) {
			try {
				await Player.threads.fetchBoards();
			} catch (err) {
				return Player.logError('Failed fetching the boards list.', err);
			}
		}
		const allThreads = [];
		try {
			await Promise.all(Player.threads.selectedBoards.map(async board => {
				const boardConf = Player.threads.boardList.find(boardConf => boardConf.board === board);
				if (!boardConf) {
					return;
				}
				const pages = boardConf && await get(catalogURL.replace('%s', board));
				(pages || []).forEach(({ page, threads }) => {
					allThreads.push(...threads.map(thread => Object.assign(thread, { board, page, ws_board: boardConf.ws_board })));
				});
			}));

			Player.threads.soundThreads = allThreads.filter(thread => {
				const sounds = parseFileName(thread.filename, `https://i.4cdn.org/${thread.board}/${thread.tim}${thread.ext}`, thread.no, `https://i.4cdn.org/${thread.board}/${thread.tim}s${thread.ext}`, thread.md5, true);
				return sounds.length;
			});
		} catch (err) {
			Player.logError('Failed searching for sounds threads.', err);
		}
		Player.threads.loading = false;
		Player.threads.filter(Player.$(`.${ns}-threads-filter`).value, true);
		Player.threads.render();
	},

	/**
	 * Apply the filter input to the already fetched threads.
	 */
	filter: function (search, skipRender) {
		search = search.toLowerCase();
		Player.threads.filterValue = search || '';
		if (Player.threads.soundThreads === null) {
			return;
		}
		Player.threads.displayThreads = Player.threads.soundThreads.reduce((threadsByBoard, thread) => {
			if (!search || thread.sub && thread.sub.toLowerCase().includes(search) || thread.com && thread.com.toLowerCase().includes(search)) {
				threadsByBoard[thread.board] || (threadsByBoard[thread.board] = []);
				threadsByBoard[thread.board].push(thread);
			}
			return threadsByBoard;
		}, {});
		!skipRender && Player.threads.renderThreads();
	}
};

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/_ */ "./src/_.js")))

/***/ }),

/***/ "./src/components/tools.js":
/*!*********************************!*\
  !*** ./src/components/tools.js ***!
  \*********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(_, Icons) {const ffmpegVersionUrl = 'https://raw.githubusercontent.com/rcc11/4chan-sounds-player/master/dist/4chan-sounds-player-with-ffmpeg.user.js';
const promoteFFmpegVersion = false;
// Seems to be the cut off point for file names
const maxFilenameLength = 218;

module.exports = {
	hasFFmpeg: typeof ffmpeg === 'function',
	_uploadIdx: 0,
	createStatusText: '',

	delegatedEvents: {
		click: {
			[`.${ns}-create-button`]: 'tools._handleCreate',
			[`.${ns}-create-sound-post-link`]: 'tools._addCreatedToQR',
			[`.${ns}-create-sound-add-link`]: 'tools._addCreatedToPlayer',
			[`.${ns}-toggle-sound-input`]: 'tools._handleToggleSoundInput',
			[`.${ns}-host-setting-link`]: _.noDefault(() => Player.settings.toggle('Hosts')),
			[`.${ns}-remove-file`]: 'tools._handleFileRemove'
		},
		change: {
			[`.${ns}-create-sound-img`]: 'tools._handleImageSelect',
			[`.${ns}-create-sound-form input[type=file]`]: e => Player.tools._handleFileSelect(e.eventTarget),
			[`.${ns}-use-video`]: 'tools._handleWebmSoundChange'
		},
		drop: {
			[`.${ns}-create-sound-form`]: 'tools._handleCreateSoundDrop'
		},
		keyup: {
			[`.${ns}-encoded-input`]: 'tools._handleEncoded',
			[`.${ns}-decoded-input`]: 'tools._handleDecoded'
		}
	},

	initialize: function () {
		Player.on('config:uploadHosts', Player.tools.render);
		Player.on('config:defaultUploadHost', newValue => Player.$(`.${ns}-create-sound-host`).value = newValue);
		Player.on('rendered', Player.tools.afterRender);
	},

	render: function () {
		Player.$(`.${ns}-tools`).innerHTML = Player.templates.tools();
		Player.tools.afterRender();
	},

	afterRender: function () {
		Player.tools.status = Player.$(`.${ns}-create-sound-status`);
		Player.tools.imgInput = Player.$(`.${ns}-create-sound-img`);
		Player.tools.sndInput = Player.$(`.${ns}-create-sound-snd`);
	},

	toggle: function (e) {
		e && e.preventDefault();
		if (Player.config.viewStyle === 'tools') {
			Player.playlist.restore();
		} else {
			Player.display.setViewStyle('tools');
		}
	},

	updateCreateStatus: function (text) {
		Player.tools.status.style.display = text ? 'inherit' : 'none';
		Player.tools.status.innerHTML = Player.tools.createStatusText = text;
	},

	/**
	 * Encode the decoded input.
	 */
	_handleDecoded: function (e) {
		Player.$(`.${ns}-encoded-input`).value = encodeURIComponent(e.eventTarget.value);
	},

	/**
	 * Decode the encoded input.
	 */
	_handleEncoded: function (e) {
		Player.$(`.${ns}-decoded-input`).value = decodeURIComponent(e.eventTarget.value);
	},

	/**
	 * Show/hide the "Use webm" checkbox when an image is selected.
	 */
	_handleImageSelect: async function (e) {
		const input = e && e.eventTarget || Player.tools.imgInput;
		const image = input.files[0];
		const isVideo = image.type === 'video/webm';
		let placeholder = image.name.replace(/\.[^/.]+$/, '');

		if (Player.tools.hasFFmpeg) {
			// Show the Use Webm label if the image is a webm file
			Player.$(`.${ns}-use-video-label`).style.display = isVideo ? 'inherit' : 'none';

			const webmCheckbox = Player.$(`.${ns}-use-video`);
			// If the image is a video and Copy Video is selected then update the sound input as well
			webmCheckbox.checked && isVideo && Player.tools._handleFileSelect(Player.tools.sndInput, [ image ]);
			// If the image isn't a webm make sure Copy Video is deselected (click to fire change event)
			webmCheckbox.checked && !isVideo && webmCheckbox.click();
		} else if (await Player.tools.hasAudio(image)) {
			Player.logError('Audio not allowed for the image webm.', null, 'warning');
		}

		// Show the image name as the placeholder for the name input since it's the default
		Player.$(`.${ns}-create-sound-name`).setAttribute('placeholder', placeholder);
	},

	/**
	 * Update the custom file input display when the input changes
	 */
	_handleFileSelect: function (input, files) {
		const container = input.closest(`.${ns}-file-input`);
		const fileText = container.querySelector('.text');
		const fileList = container.querySelector(`.${ns}-file-list`);
		files || (files = [ ...input.files ]);
		container.classList[files.length ? 'remove' : 'add']('placeholder');
		fileText.innerHTML = files.length > 1
			? files.length + ' files'
			: files[0] && files[0].name || '';
		fileList && (fileList.innerHTML = files.length < 2 ? '' : files.map((file, i) =>
			`<div class="${ns}-row">
				<div class="${ns}-col ${ns}-truncate-text">${file.name}</div>
				<a class="${ns}-col-auto ${ns}-remove-file" href="#" data-idx="${i}">${Icons.close}</a>
			</div>`
		).join(''));
	},

	/**
	 * Handle a file being removed from a multi input
	 */
	_handleFileRemove: function (e) {
		e.preventDefault();
		const idx = +e.eventTarget.getAttribute('data-idx');
		const input = e.eventTarget.closest(`.${ns}-file-input`).querySelector('input[type="file"]');
		const dataTransfer = new DataTransfer();
		for (let i = 0; i < input.files.length; i++) {
			i !== idx && dataTransfer.items.add(input.files[i]);
		}
		input.files = dataTransfer.files;
		Player.tools._handleFileSelect(input);
	},

	/**
	 * Show/hide the sound input when "Use webm" is changed.
	 */
	_handleWebmSoundChange: function (e) {
		const sound = Player.tools.sndInput;
		const image = Player.tools.imgInput;
		Player.tools._handleFileSelect(sound, e.eventTarget.checked && [ image.files[0] ]);
	},

	_handleToggleSoundInput: function (e) {
		e.preventDefault();
		const showURL = e.eventTarget.getAttribute('data-type') === 'url';
		Player.$(`.${ns}-create-sound-snd-url`).closest(`.${ns}-row`).style.display = showURL ? null : 'none';
		Player.$(`.${ns}-create-sound-snd`).closest(`.${ns}-file-input`).style.display = showURL ? 'none' : null;
		Player.tools.useSoundURL = showURL;
	},

	/**
	 * Handle files being dropped on the create sound section.
	 */
	_handleCreateSoundDrop: function (e) {
		e.preventDefault();
		e.stopPropagation();
		const targetInput = e.target.nodeName === 'INPUT' && e.target.getAttribute('type') === 'file' && e.target;
		[ ...e.dataTransfer.files ].forEach(file => {
			const isVideo = file.type.startsWith('video');
			const isImage = file.type.startsWith('image') || file.type === 'video/webm';
			const isSound = file.type.startsWith('audio');
			if (isVideo || isImage || isSound) {
				const input = file.type === 'video/webm' && targetInput
					? targetInput
					: isImage
						? Player.tools.imgInput
						: Player.tools.sndInput;
				const dataTransfer = new DataTransfer();
				if (input.multiple) {
					[ ...input.files ].forEach(file => dataTransfer.items.add(file));
				}
				dataTransfer.items.add(file);
				input.files = dataTransfer.files;
				Player.tools._handleFileSelect(input);
				input === Player.tools.imgInput && Player.tools._handleImageSelect();
			}
		});
		return false;
	},

	/**
	 * Handle the create button.
	 * Extracts video/audio if required, uploads the sound, and creates an image file names with [sound=url].
	 */
	_handleCreate: async function (e) {
		e && e.preventDefault();
		// Revoke the URL for an existing created image.
		Player.tools._createdImageURL && URL.revokeObjectURL(Player.tools._createdImageURL);
		Player.tools._createdImage = null;

		Player.tools.updateCreateStatus('Creating sound image');

		Player.$(`.${ns}-create-button`).disabled = true;

		// Gather the input values.
		const host =  Player.config.uploadHosts[Player.$(`.${ns}-create-sound-host`).value];
		const useSoundURL = Player.tools.useSoundURL;
		let image = Player.tools.imgInput.files[0];
		let soundURLs = useSoundURL && Player.$(`.${ns}-create-sound-snd-url`).value.split(',').map(v => v.trim()).filter(v => v);
		let sounds = !(Player.$(`.${ns}-use-video`) || {}).checked || !image || !image.type.startsWith('video')
			? [ ...Player.tools.sndInput.files ]
			: image && [ image ];
		const customName = Player.$(`.${ns}-create-sound-name`).value;
		// Only split a given name if there's multiple sounds.
		const names = customName
			? ((soundURLs || sounds).length > 1 ? customName.split(',') : [ customName ]).map(v => v.trim())
			: image && [ image.name.replace(/\.[^/.]+$/, '') ];

		try {
			if (!image) {
				throw new PlayerError('Select an image or webm.', 'warning');
			}

			if (image.type.startsWith('video') && await Player.tools.hasAudio(image)) {
				// If ffmpeg is not available fall out.
				if (!Player.tools.hasFFmpeg) {
					Player.tools.updateCreateStatus(Player.tools.createStatusText
						+ '<br>' + (promoteFFmpegVersion ? 'This version of the player does not enable webm splitting.' : 'Audio not allowed for the image webm.')
						+ '<br>Remove the audio from the webm and try again.'
						+ (promoteFFmpegVersion ? `<br>Alternatively install the <a href="${ffmpegVersionUrl}">ffmpeg version</a> to extract video/audio automatically.` : ''));
					throw new PlayerError('Audio not allowed for the image webm.', 'warning');
				}

				// If the image is a webm with audio then extract just the video.
				image = await Player.tools.extract(image, 'video');
			}

			const soundlessLength = names.join('').length + (soundURLs || sounds).length * 8;
			if (useSoundURL) {
				try {
					// Make sure each url is valid and strip the protocol.
					soundURLs = soundURLs.map(url => new URL(url) && url.replace(/^(https?:)?\/\//, ''));
				} catch (err) {
					throw new PlayerError('The provided sound URL is invalid.', 'warning');
				}
				if (maxFilenameLength < soundlessLength + soundURLs.join('').length) {
					throw new PlayerError('The generated image filename is too long.', 'warning');
				}
			} else {
				if (!sounds || !sounds.length) {
					throw new PlayerError('Select a sound.', 'warning');
				}

				// Check the final filename length if the URL length is known for the host.
				// Limit to 8 otherwise. zz.ht is as small as you're likely to get and that can only fit 8.
				const tooManySounds = host.filenameLength
					? maxFilenameLength < soundlessLength + (host.filenameLength) * sounds.length
					: sounds.length > 8;
				if (tooManySounds) {
					throw new PlayerError('The generated image filename is too long.', 'warning');
				}

				// Check videos have audio and extract it if possible.
				sounds = await Promise.all(sounds.map(async sound => {
					if (sound.type.startsWith('video')) {
						if (!await Player.tools.hasAudio(sound)) {
							throw new PlayerError(`The selected video has no audio. (${sound.name})`, 'warning');
						}
						if (Player.tools.hasFFmpeg) {
							return await Player.tools.extract(sound, 'audio');
						}
					}
					return sound;
				}));

				// Upload the sounds.
				try {
					soundURLs = await Promise.all(sounds.map(async sound => Player.tools.postFile(sound, host)));
				} catch (err) {
					throw new PlayerError('Upload failed.', 'error', err);
				}
			}

			if (!soundURLs.length) {
				throw new PlayerError('No sounds selected.', 'warning');
			}

			// Create a new file that includes [sound=url] in the name.
			let filename = '';
			for (let i = 0; i < soundURLs.length; i++) {
				filename += (names[i] || '') + '[sound=' + encodeURIComponent(soundURLs[i].replace(/^(https?:)?\/\//, '')) + ']';
			}
			const ext = image.name.match(/\.([^/.]+)$/)[1];
			const soundImage = new File([ image ], filename + '.' + ext, { type: image.type });

			// Keep track of the create image and a url to it.
			Player.tools._createdImage = soundImage;
			Player.tools._createdImageURL = URL.createObjectURL(soundImage);

			// Complete! with some action links
			Player.tools.updateCreateStatus(Player.tools.createStatusText
				+ '<br>Complete!<br>'
				+ (is4chan ? `<a href="#" class="${ns}-create-sound-post-link">Post</a> - ` : '')
				+ ` <a href="#" class="${ns}-create-sound-add-link">Add</a> - `
				+ ` <a href="${Player.tools._createdImageURL}" download="${soundImage.name}" title="${soundImage.name}">Download</a>`
			);
		} catch (err) {
			Player.tools.updateCreateStatus(Player.tools.createStatusText
				+ '<br>Failed! ' + (err instanceof PlayerError ? err.reason : ''));
			Player.logError('Failed to create sound image', err);
		}
		Player.$(`.${ns}-create-button`).disabled = false;
	},

	hasAudio: function (file) {
		if (!file.type.startsWith('audio') && !file.type.startsWith('video')) {
			return false;
		}
		return new Promise((resolve, reject) => {
			const url = URL.createObjectURL(file);
			const video = document.createElement('video');
			video.addEventListener('loadeddata', () => {
				URL.revokeObjectURL(url);
				resolve(video.mozHasAudio || !!video.webkitAudioDecodedByteCount);
			});
			video.addEventListener('error', reject);
			video.src = url;
		});
	},

	/**
	 * Extract just the audio or video from a file.
	 */
	extract: async function (file, type) {
		Player.tools.updateCreateStatus(Player.tools.createStatusText + '<br>Extracting ' + type);
		if (typeof ffmpeg !== 'function') {
			return file;
		}
		const name = file.name.replace(/\.[^/.]+$/, '') + (type === 'audio' ? '.ogg' : '.webm');

		const result = ffmpeg({
			MEMFS: [ { name: '_' + file.name, data: await new Response(file).arrayBuffer() } ],
			arguments: type === 'audio'
				? [ '-i', '_' + file.name, '-vn', '-c', 'copy', name ]
				: [ '-i', '_' + file.name, '-an', '-c', 'copy', name ]
		});

		return new File([ result.MEMFS[0].data ], name, { type: type === 'audio' ? 'audio/ogg' : 'video/webm' });
	},

	/**
	 * Upload the sound file and return a link to it.
	 */
	postFile: async function (file, host) {
		const idx = Player.tools._uploadIdx++;

		if (!host || host.invalid) {
			throw new PlayerError('Invalid upload host.', 'error');
		}

		const formData = new FormData();
		Object.keys(host.data).forEach(key => {
			if (host.data[key] !== null) {
				formData.append(key, host.data[key] === '$file' ? file : host.data[key]);
			}
		});

		Player.tools.updateCreateStatus(Player.tools.createStatusText + `<br><span class="${ns}-upload-status-${idx}">Uploading ${file.name}</span>`);

		return new Promise((resolve, reject) => {
			GM.xmlHttpRequest({
				method: 'POST',
				url: host.url,
				data: formData,
				responseType: host.responsePath ? 'json' : 'text',
				headers: host.headers,
				onload: async response => {
					if (response.status < 200 || response.status >= 300) {
						return reject(response);
					}
					const responseVal = host.responsePath
						? _.get(response.response, host.responsePath)
						: host.responseMatch
							? (response.responseText.match(new RegExp(host.responseMatch)) || [])[1]
							: response.responseText;
					const uploadedUrl = host.soundUrl ? host.soundUrl.replace('%s', responseVal) : responseVal;
					Player.$(`.${ns}-upload-status-${idx}`).innerHTML = `Uploaded ${file.name} to <a href="${uploadedUrl}" target="_blank">${uploadedUrl}</a>`;
					Player.tools.createStatusText = Player.tools.status.innerHTML;
					resolve(uploadedUrl);
				},
				upload: {
					onprogress: response => {
						const total = response.total > 0 ? response.total : file.size;
						Player.$(`.${ns}-upload-status-${idx}`).innerHTML = `Uploading ${file.name} - ${Math.floor(response.loaded / total * 100)}%`;
					}
				},
				onerror: reject
			});
		});
	},

	/**
	 * Add the created sound image to the player.
	 */
	_addCreatedToPlayer: function (e) {
		e.preventDefault();
		Player.playlist.addFromFiles([ Player.tools._createdImage ]);
	},

	/**
	 * Open the QR window and add the created sound image to it.
	 */
	_addCreatedToQR: function (e) {
		if (!is4chan) {
			return;
		}
		e.preventDefault();
		// Open the quick reply window.
		const qrLink = document.querySelector(isChanX ? '.qr-link' : '.open-qr-link');

		const dataTransfer = new DataTransfer();
		dataTransfer.items.add(Player.tools._createdImage);

		// 4chan X, drop the file on the qr window.
		if (isChanX) {
			qrLink.click();
			const event = new CustomEvent('drop', { view: window, bubbles: true, cancelable: true });
			event.dataTransfer = dataTransfer;
			document.querySelector('#qr').dispatchEvent(event);

		// Native, set the file input value. Check for a quick reply
		} else if (qrLink) {
			qrLink.click();
			document.querySelector('#qrFile').files = dataTransfer.files;
		} else {
			document.querySelector('#togglePostFormLink a').click();
			document.querySelector('#postFile').files = dataTransfer.files;
			document.querySelector('.postForm').scrollIntoView();
		}
	},
};

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/_ */ "./src/_.js"), __webpack_require__(/*! ./src/icons */ "./src/icons.js")))

/***/ }),

/***/ "./src/components/user-template/buttons.js":
/*!*************************************************!*\
  !*** ./src/components/user-template/buttons.js ***!
  \*************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(Icons) {const { postIdPrefix } = __webpack_require__(/*! ../../selectors */ "./src/selectors.js");

module.exports = [
	{
		property: 'repeat',
		tplName: 'repeat',
		class: `${ns}-repeat-button`,
		values: {
			all: { attrs: [ 'title="Repeat All"' ], icon: Icons.arrowRepeat },
			one: { attrs: [ 'title="Repeat One"' ], icon: Icons.arrowClockwise },
			none: { attrs: [ 'title="No Repeat"' ], class: 'muted', icon: Icons.arrowRepeat }
		}
	},
	{
		property: 'shuffle',
		tplName: 'shuffle',
		class: `${ns}-shuffle-button`,
		values: {
			true: { attrs: [ 'title="Shuffled"' ], icon: Icons.shuffle },
			false: { attrs: [ 'title="Ordered"' ], class: 'muted', icon: Icons.shuffle }
		}
	},
	{
		property: 'viewStyle',
		tplName: 'playlist',
		class: `${ns}-viewStyle-button`,
		values: {
			default: { attrs: [ 'title="Player"' ], class: 'muted', icon: () => (Player.playlist._lastView === 'playlist' ? Icons.arrowsExpand : Icons.arrowsCollapse) },
			playlist: { attrs: [ 'title="Hide Playlist"' ], icon: Icons.arrowsExpand },
			image: { attrs: [ 'title="Show Playlist"' ], icon: Icons.arrowsCollapse }
		}
	},
	{
		property: 'hoverImages',
		tplName: 'hover-images',
		class: `${ns}-hoverImages-button`,
		values: {
			true: { attrs: [ 'title="Hover Images Enabled"' ], icon: Icons.image },
			false: { attrs: [ 'title="Hover Images Disabled"' ], class: 'muted', icon: Icons.image }
		}
	},
	{
		tplName: 'add',
		class: `${ns}-add-button`,
		icon: Icons.plus,
		attrs: [ 'title="Add local files"' ]
	},
	{
		tplName: 'reload',
		class: `${ns}-reload-button`,
		icon: Icons.reboot,
		attrs: [ 'title="Reload the playlist"' ]
	},
	{
		property: 'viewStyle',
		tplName: 'settings',
		class: `${ns}-config-button`,
		icon: Icons.gear,
		attrs: [ 'title="Settings"' ],
		values: {
			default: { class: 'muted' },
			settings: { }
		}
	},
	{
		property: 'viewStyle',
		tplName: 'threads',
		class: `${ns}-threads-button`,
		icon: Icons.search,
		attrs: [ 'title="Threads"' ],
		values: {
			default: { class: 'muted' },
			threads: { }
		}
	},
	{
		property: 'viewStyle',
		tplName: 'tools',
		class: `${ns}-tools-button`,
		icon: Icons.tools,
		attrs: [ 'title="Tools"' ],
		values: {
			default: { class: 'muted' },
			tools: { }
		}
	},
	{
		tplName: 'close',
		class: `${ns}-close-button`,
		icon: Icons.close,
		attrs: [ 'title="Hide the player"' ]
	},
	{
		tplName: 'playing',
		requireSound: true,
		class: `${ns}-playing-jump-link`,
		icon: Icons.musicNoteList,
		attrs: [ 'title="Scroll the playlist currently playing sound."' ]
	},
	{
		tplName: 'post',
		requireSound: true,
		icon: Icons.chatRightQuote,
		showIf: data => data.sound.post,
		attrs: data => [
			`href=${'#' + postIdPrefix + data.sound.post}`,
			'title="Jump to the post for the current sound"'
		]
	},
	{
		tplName: 'image',
		requireSound: true,
		icon: Icons.image,
		attrs: data => [
			`href=${data.sound.image}`,
			'title="Open the image in a new tab"',
			'target="_blank"'
		]
	},
	{
		tplName: 'sound',
		requireSound: true,
		href: data => data.sound.src,
		icon: Icons.soundwave,
		attrs: data => [
			`href=${data.sound.src}`,
			'title="Open the sound in a new tab"',
			'target="_blank"'
		]
	},
	{
		tplName: 'dl-image',
		requireSound: true,
		class: `${ns}-download-link`,
		icon: Icons.fileEarmarkImage,
		attrs: data => [
			'title="Download the image with the original filename"',
			`data-src="${data.sound.image}"`,
			`data-name="${data.sound.filename}"`
		]
	},
	{
		tplName: 'dl-sound',
		requireSound: true,
		class: `${ns}-download-link`,
		icon: Icons.fileEarmarkMusic,
		attrs: data => [
			'title="Download the sound"',
			`data-src="${data.sound.src}"`,
			`data-name="${data.sound.name}"`
		]
	},
	{
		tplName: 'filter-image',
		requireSound: true,
		class: `${ns}-filter-link`,
		icon: Icons.filter,
		showIf: data => data.sound.imageMD5,
		attrs: data => [
			'title="Add the image MD5 to the filters."',
			`data-filter="${data.sound.imageMD5}"`
		]
	},
	{
		tplName: 'filter-sound',
		requireSound: true,
		class: `${ns}-filter-link`,
		icon: Icons.filter,
		attrs: data => [
			'title="Add the sound URL to the filters."',
			`data-filter="${data.sound.src.replace(/^(https?:)?\/\//, '')}"`
		]
	},
	{
		tplName: 'remove',
		requireSound: true,
		class: `${ns}-remove-link`,
		icon: Icons.trash,
		attrs: data => [
			'title="Filter the image."',
			`data-id="${data.sound.id}"`
		]
	},
	{
		tplName: 'menu',
		requireSound: true,
		class: `${ns}-item-menu-button`,
		icon: Icons.chevronDown,
		attrs: data => [ `data-id=${data.sound.id}` ]
	},
	{
		tplName: 'view-menu',
		class: `${ns}-view-menu-button`,
		icon: Icons.chevronDown,
		attrs: [ 'href="javascript:;"' ]
	}
];

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/icons */ "./src/icons.js")))

/***/ }),

/***/ "./src/components/user-template/index.js":
/*!***********************************************!*\
  !*** ./src/components/user-template/index.js ***!
  \***********************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(_) {const buttons = __webpack_require__(/*! ./buttons */ "./src/components/user-template/buttons.js");

// Regex for replacements
const playingRE = /p: ?{([^}]*)}/g;
const hoverRE = /h: ?{([^}]*)}/g;
const buttonRE = new RegExp(`(${buttons.map(option => option.tplName).join('|')})-(?:button|link)(?:\\:"([^"]+?)")?`, 'g');
const soundNameRE = /sound-name/g;
const soundNameMarqueeRE = /sound-name-marquee/g;
const soundIndexRE = /sound-index/g;
const soundCountRE = /sound-count/g;

// Hold information on which config values components templates depend on.
const componentDeps = [ ];

module.exports = {
	buttons,

	delegatedEvents: {
		click: {
			[`.${ns}-playing-jump-link`]: () => Player.playlist.scrollToPlaying('center'),
			[`.${ns}-viewStyle-button`]: 'playlist.toggleView',
			[`.${ns}-hoverImages-button`]: 'playlist.toggleHoverImages',
			[`.${ns}-remove-link`]: 'userTemplate._handleRemove',
			[`.${ns}-filter-link`]: 'userTemplate._handleFilter',
			[`.${ns}-download-link`]: 'userTemplate._handleDownload',
			[`.${ns}-shuffle-button`]: 'userTemplate._handleShuffle',
			[`.${ns}-repeat-button`]: 'userTemplate._handleRepeat',
			[`.${ns}-reload-button`]: _.noDefault('playlist.refresh'),
			[`.${ns}-add-button`]: _.noDefault(() => Player.$(`.${ns}-add-local-file-input`).click()),
			[`.${ns}-item-menu-button`]: 'playlist._handleItemMenu',
			[`.${ns}-view-menu-button`]: 'userTemplate._handleViewsMenu',
			[`.${ns}-threads-button`]: 'threads.toggle',
			[`.${ns}-tools-button`]: 'tools.toggle',
			[`.${ns}-config-button`]: _.noDefault(() => Player.settings.toggle()),
			[`.${ns}-favorites-button`]: 'favorites.toggle',
			[`.${ns}-player-button`]: 'playlist.restore'
		},
		change: {
			[`.${ns}-add-local-file-input`]: 'userTemplate._handleFileSelect'
		}
	},

	initialize: function () {
		Player.on('config', Player.userTemplate._handleConfig);
		Player.on('playsound', () => Player.userTemplate._handleEvent('playsound'));
		[ 'add', 'remove', 'order', 'show', 'hide', 'stop' ].forEach(evt => {
			Player.on(evt, Player.userTemplate._handleEvent.bind(null, evt));
		});
	},

	/**
	 * Build a user template.
	 */
	build: function (data) {
		const outerClass = data.outerClass || '';
		const name = data.sound && data.sound.title || data.defaultName;

		const _confFuncOrText = v => (typeof v === 'function' ? v(data) : v);

		// Apply common template replacements
		let html = data.template
			.replace(playingRE, Player.playing && Player.playing === data.sound ? '$1' : '')
			.replace(hoverRE, `<span class="${ns}-hover-display ${outerClass}">$1</span>`)
			.replace(buttonRE, function (full, type, text) {
				let buttonConf = buttons.find(conf => conf.tplName === type);
				if (buttonConf.requireSound && !data.sound || buttonConf.showIf && !buttonConf.showIf(data)) {
					return '';
				}
				// If the button config has sub values then extend the base config with the selected sub value.
				// Which value is to use is taken from the `property` in the base config of the player config.
				// This gives us different state displays.
				if (buttonConf.values) {
					let topConf = buttonConf;
					const valConf = buttonConf.values[_.get(Player.config, buttonConf.property)] || buttonConf.values[Object.keys(buttonConf.values)[0]];
					buttonConf = { ...topConf, ...valConf, class: ((topConf.class || '') + ' ' + (valConf.class || '')).trim() };
				}
				const attrs = [ ...(_confFuncOrText(buttonConf.attrs) || []) ];
				attrs.some(attr => attr.startsWith('href')) || attrs.push('href="javascript:;"');
				(buttonConf.class || outerClass) && attrs.push(`class="${buttonConf.class || ''} ${outerClass || ''}"`);

				return `<a ${attrs.join(' ')}>${text || _confFuncOrText(buttonConf.icon) || _confFuncOrText(buttonConf.text)}</a>`;
			})
			.replace(soundNameMarqueeRE, name ? `<div class="${ns}-col ${ns}-truncate-text" style="margin: 0 .5rem; text-overflow: clip;"><span title="${name}" class="${ns}-title-marquee" data-location="${data.location || ''}">${name}</span></div>` : '')
			.replace(soundNameRE, name ? `<div class="${ns}-col ${ns}-truncate-text" style="margin: 0 .5rem"><span title="${name}">${name}</span></div>` : '')
			.replace(soundIndexRE, data.sound ? Player.sounds.indexOf(data.sound) + 1 : 0)
			.replace(soundCountRE, Player.sounds.length)
			.replace(/%v/g, "3.2.1");

		// Apply any specific replacements
		if (data.replacements) {
			for (let k of Object.keys(data.replacements)) {
				html = html.replace(new RegExp(k, 'g'), data.replacements[k]);
			}
		}

		return html;
	},

	/**
	 * Sets up a components to render when the template or values within it are changed.
	 */
	maintain: function (component, property, alwaysRenderConfigs = [], alwaysRenderEvents = []) {
		componentDeps.push({
			component,
			property,
			...Player.userTemplate.findDependencies(property, null),
			alwaysRenderConfigs,
			alwaysRenderEvents
		});
	},

	/**
	 * Find all the config dependent values in a template.
	 */
	findDependencies: function (property, template) {
		template || (template = _.get(Player.config, property));
		// Figure out what events should trigger a render.
		const events = [];

		// add/remove should render templates showing the count.
		// playsound/stop should render templates showing the playing sounds name/index or dependent on something playing.
		// order should render templates showing a sounds index.
		const hasCount = soundCountRE.test(template);
		const hasName = soundNameRE.test(template);
		const hasIndex = soundIndexRE.test(template);
		const hasPlaying = playingRE.test(template);
		hasCount && events.push('add', 'remove');
		(hasPlaying || property !== 'rowTemplate' && (hasName || hasIndex)) && events.push('playsound', 'stop');
		hasIndex && events.push('order');

		// Find which buttons the template includes that are dependent on config values.
		const config = [];
		let match;
		while ((match = buttonRE.exec(template)) !== null) {
			// If user text is given then the display doesn't change.
			if (!match[2]) {
				let type = match[1];
				let buttonConf = buttons.find(conf => conf.tplName === type);
				if (buttonConf.property) {
					config.push(buttonConf.property);
				}
			}
		}

		return { events, config };
	},

	/**
	 * When a config value is changed check if any component dependencies are affected.
	 */
	_handleConfig: function (property, value) {
		// Check if a template for a components was updated.
		componentDeps.forEach(depInfo => {
			if (depInfo.property === property) {
				Object.assign(depInfo, Player.userTemplate.findDependencies(property, value));
				depInfo.component.render();
			}
		});
		// Check if any components are dependent on the updated property.
		componentDeps.forEach(depInfo => {
			if (depInfo.alwaysRenderConfigs.includes(property) || depInfo.config.includes(property)) {
				depInfo.component.render();
			}
		});
	},

	/**
	 * When a player event is triggered check if any component dependencies are affected.
	 */
	_handleEvent: function (type) {
		// Check if any components are dependent on the updated property.
		componentDeps.forEach(depInfo => {
			if (depInfo.alwaysRenderEvents.includes(type) || depInfo.events.includes(type)) {
				depInfo.component.render();
			}
		});
	},

	/**
	 * Add local files.
	 */
	_handleFileSelect: function (e) {
		e.preventDefault();
		const input = e.eventTarget;
		Player.playlist.addFromFiles(input.files);
	},

	/**
	 * Toggle the repeat style.
	 */
	_handleRepeat: function (e) {
		e.preventDefault();
		const values = [ 'all', 'one', 'none' ];
		const current = values.indexOf(Player.config.repeat);
		Player.set('repeat', values[(current + 4) % 3]);
	},

	/**
	 * Toggle the shuffle style.
	 */
	_handleShuffle: function (e) {
		e.preventDefault();
		Player.set('shuffle', !Player.config.shuffle);
		Player.header.render();

		// Update the play order.
		if (!Player.config.shuffle) {
			Player.sounds.sort((a, b) => Player.compareIds(a.id, b.id));
		} else {
			const sounds = Player.sounds;
			for (let i = sounds.length - 1; i > 0; i--) {
				const j = Math.floor(Math.random() * (i + 1));
				[ sounds[i], sounds[j] ] = [ sounds[j], sounds[i] ];
			}
		}
		Player.trigger('order');
	},

	_handleViewsMenu: function (e) {
		e.preventDefault();
		e.stopPropagation();
		const dialog = _.element(Player.templates.viewsMenu());
		Player.userTemplate._showMenu(e.eventTarget, dialog);
	},

	_showMenu: function (relative, dialog, parent) {
		Player.display.closeDialogs();
		parent || (parent = Player.container);
		parent.appendChild(dialog);

		// Position the menu.
		Player.position.showRelativeTo(dialog, relative);

		// Add the focused class handler
		dialog.querySelectorAll('.entry').forEach(el => {
			el.addEventListener('mouseenter', Player.userTemplate._setFocusedMenuItem);
		});

		Player.trigger('menu-open', dialog);
	},

	_setFocusedMenuItem: function (e) {
		const submenu = e.currentTarget.querySelector('.submenu');
		const menu = e.currentTarget.closest('.dialog');
		const currentFocus = menu.querySelectorAll('.focused');
		currentFocus.forEach(el => el.classList.remove('focused'));
		e.currentTarget.classList.add('focused');
		// Move the menu to the other side if there isn't room.
		if (submenu && submenu.getBoundingClientRect().right > document.documentElement.clientWidth) {
			submenu.style.inset = '0px 100% auto auto';
		}
	},

	_handleFilter: function (e) {
		e.preventDefault();
		let filter = e.eventTarget.getAttribute('data-filter');
		if (filter) {
			Player.set('filters', Player.config.filters.concat(filter));
		}
	},

	_handleDownload: function (e) {
		const src = e.eventTarget.getAttribute('data-src');
		const name = e.eventTarget.getAttribute('data-name') || new URL(src).pathname.split('/').pop();

		GM.xmlHttpRequest({
			method: 'GET',
			url: src,
			responseType: 'blob',
			onload: response => {
				const a = _.element(`<a href="${URL.createObjectURL(response.response)}" download="${name}" rel="noopener" target="_blank"></a>`);
				a.click();
				URL.revokeObjectURL(a.href);
			},
			onerror: response => Player.logError('There was an error downloading.', response, 'warning')
		});
	},

	_handleRemove: function (e) {
		const id = e.eventTarget.getAttribute('data-id');
		const sound = id && Player.sounds.find(sound => sound.id === '' + id);
		sound && Player.remove(sound);
	},
};

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/_ */ "./src/_.js")))

/***/ }),

/***/ "./src/config/display.js":
/*!*******************************!*\
  !*** ./src/config/display.js ***!
  \*******************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = [
	{
		property: 'autoshow',
		default: true,
		title: 'Autoshow',
		description: 'Automatically show the player when the thread contains sounds.',
		displayGroup: 'Display'
	},
	{
		property: 'pauseOnHide',
		default: true,
		title: 'Pause On Hide',
		description: 'Pause the player when it\'s hidden.',
		displayGroup: 'Display'
	},
	{
		property: 'showUpdatedNotification',
		default: true,
		title: 'Show Update Notifications',
		description: 'Show notifications when the player is successfully updated.',
		displayGroup: 'Display'
	},
	{
		title: 'Controls',
		displayGroup: 'Display',
		settings: [
			{
				property: 'preventControlWrapping',
				title: 'Prevent Wrapping',
				description: 'Hide controls to prevent wrapping when the player is too small',
				default: true
			},
			{
				property: 'controlsHideOrder',
				title: 'Hide Order',
				description: 'Order controls are hidden in to prevent wrapping. Available controls are previous, next, seek-bar, time, duration, volume, mute, volume-bar, and fullscreen.',
				default: [ 'fullscreen', 'duration', 'volume-bar', 'seek-bar', 'time', 'previous' ],
				displayMethod: (value, attrs) => `<div class="${ns}-col"><textarea ${attrs}>${value}</textarea></div>`,
				format: v => v.join('\n'),
				parse: v => v.split(/\s+/)
			}
		]
	},
	{
		title: 'Minimised Display',
		description: 'Optional displays for when the player is minimised.',
		displayGroup: 'Display',
		settings: [
			{
				property: 'pip',
				title: 'Thumbnail',
				description: 'Display a fixed thumbnail of the playing sound in the bottom right of the thread.',
				default: true
			},
			{
				property: 'maxPIPWidth',
				title: 'Max Width',
				description: 'Maximum width for the thumbnail.',
				default: '150px',
				updateStylesheet: true
			},
			{
				property: 'chanXControls',
				title: '4chan X Header Controls',
				description: 'Show playback controls in the 4chan X header. The display can be customised in Templates.',
				displayMethod: isChanX || null,
				options: {
					always: 'Always',
					closed: 'Only with the player closed',
					never: 'Never'
				}
			}
		]
	},
	{
		title: 'Threads',
		displayGroup: 'Display',
		settings: [
			{
				property: 'autoScrollThread',
				description: 'Automatically scroll the thread to posts as sounds play.',
				title: 'Auto Scroll',
				default: false
			},
			{
				property: 'limitPostWidths',
				description: 'Limit the width of posts so they aren\'t hidden under the player.',
				title: 'Limit Post Widths',
				default: true
			},
			{
				property: 'minPostWidth',
				title: 'Minimum Width',
				default: '50%'
			}
		]
	},
	{
		property: 'threadsViewStyle',
		title: 'Threads View',
		description: 'How threads in the threads view are listed.',
		displayGroup: 'Display',
		settings: [ {
			title: 'Display',
			default: 'table',
			options: {
				table: 'Table',
				board: 'Board'
			}
		} ]
	},
	{
		title: 'Colors',
		displayGroup: 'Display',
		property: 'colors',
		updateStylesheet: true,
		class: `${ns}-colorpicker-input`,
		displayMethod: (value, attrs) => `<div class="${ns}-col">
				<input type="text" ${attrs} value="${value}">
				<div class="${ns}-cp-preview" style="background: ${value}"></div>
			</div>`,
		actions: [
			{ title: 'Match Theme', handler: 'display.forceBoardTheme' }
		],
		// These colors will be overriden with the theme defaults at initialization.
		settings: [
			{
				property: 'colors.text',
				default: '#000000',
				title: 'Text'
			},
			{
				property: 'colors.background',
				default: '#d6daf0',
				title: 'Background'
			},
			{
				property: 'colors.border',
				default: '#b7c5d9',
				title: 'Border'
			},
			{
				property: 'colors.odd_row',
				default: '#d6daf0',
				title: 'Odd Row',
			},
			{
				property: 'colors.even_row',
				default: '#b7c5d9',
				title: 'Even Row'
			},
			{
				property: 'colors.playing',
				default: '#98bff7',
				title: 'Playing Row'
			},
			{
				property: 'colors.dragging',
				default: '#c396c8',
				title: 'Dragging Row'
			},
			{
				property: 'colors.controls_background',
				default: '#3f3f44',
				title: 'Controls Background',
				description: 'The controls container element background.',
				actions: [ { title: 'Reset', handler: 'settings.reset' } ],
			},
			{
				property: 'colors.controls_inactive',
				default: '#FFFFFF',
				title: 'Control Items',
				description: 'The playback controls and played bar.',
				actions: [ { title: 'Reset', handler: 'settings.reset' } ],
			},
			{
				property: 'colors.controls_active',
				default: '#00b6f0',
				title: 'Focused Control Items',
				description: 'The control items when hovered.',
				actions: [ { title: 'Reset', handler: 'settings.reset' } ],
			},
			{
				property: 'colors.controls_empty_bar',
				default: '#131314',
				title: 'Volume/Seek Bar Background',
				decscription: 'The background of the volume and seek bars.',
				actions: [ { title: 'Reset', handler: 'settings.reset' } ],
			},
			{
				property: 'colors.controls_loaded_bar',
				default: '#5a5a5b',
				title: 'Loaded Bar Background',
				description: 'The loaded bar within the seek bar.',
				actions: [ { title: 'Reset', handler: 'settings.reset' } ],
			}
		]
	}
];


/***/ }),

/***/ "./src/config/filter.js":
/*!******************************!*\
  !*** ./src/config/filter.js ***!
  \******************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = [
	{
		property: 'addWebm',
		title: 'Include WebM',
		description: 'Whether to add all WebM files regardless of a sound filename.',
		default: 'soundBoards',
		displayGroup: 'Filter',
		options: {
			always: 'Always',
			soundBoards: 'Boards with sound',
			never: 'Never'
		}
	},
	{
		property: 'allow',
		title: 'Allowed Hosts',
		description: 'Which domains sources are allowed to be loaded from.',
		default: [
			'4cdn.org',
			'catbox.moe',
			'dmca.gripe',
			'lewd.se',
			'pomf.cat',
			'zz.ht'
		],
		actions: [ { title: 'Reset', handler: 'settings.reset' } ],
		displayGroup: 'Filter',
		split: '\n'
	},
	{
		property: 'filters',
		default: [ '# Image MD5 or sound URL' ],
		title: 'Filters',
		description: 'List of URLs or image MD5s to filter, one per line.\nLines starting with a # will be ignored.',
		actions: [ { title: 'Reset', handler: 'settings.reset' } ],
		displayGroup: 'Filter',
		split: '\n'
	}
];


/***/ }),

/***/ "./src/config/hosts.js":
/*!*****************************!*\
  !*** ./src/config/hosts.js ***!
  \*****************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = [
	{
		property: 'defaultUploadHost',
		default: 'catbox',
		parse: 'settings.setDefaultHost'
	},
	{
		property: 'uploadHosts',
		title: 'Hosts',
		actions: [
			{ title: 'Add', handler: 'settings.addUploadHost' },
			{ title: 'Restore Defaults', handler: 'settings.restoreDefaultHosts' },
		],
		displayGroup: 'Hosts',
		displayMethod: 'settings.renderHosts',
		parse: 'settings.parseHosts',
		looseCompare: true,
		dismissTextId: 'uplodHostSettings',
		dismissRestoreText: 'Show Help',
		text: 'Properties'
			+ '<br><strong>Name</strong>: A unique identifier.'
			+ '<br><strong>URL</strong>: The URL to post the file to.'
			+ '<br><strong>Response Path/Match</strong>: A key path or regular expression to locate the uploaded filename in the response.'
			+ '<br><strong>File URL Format</strong>: The URL format for uploaded sounds. %s is replaced with the result of response path/match if given or the full response.'
			+ '<br><strong>Data</strong>: The form data for the upload (as JSON). Specify the file using $file.',
		mix: true,
		default:  {
			catbox: {
				default: true,
				url: 'https://catbox.moe/user/api.php',
				data: { reqtype: 'fileupload', fileToUpload: '$file', userhash: null },
				filenameLength: 29
			},
			pomf: {
				url: 'https://pomf.cat/upload.php',
				data: { 'files[]': '$file' },
				responsePath: 'files.0.url',
				filenameLength: 23
			},
			zz: {
				url: 'https://zz.ht/api/upload',
				responsePath: 'files.0.url',
				data: {
					'files[]': '$file'
				},
				headers: {
					token: null
				},
				filenameLength: 19
			},
			lewd: {
				url: 'https://lewd.se/upload',
				data: { file: '$file' },
				headers: { token: null, shortUrl: true },
				responsePath: 'data.link',
				filenameLength: 30
			}
		}
	}
];


/***/ }),

/***/ "./src/config/index.js":
/*!*****************************!*\
  !*** ./src/config/index.js ***!
  \*****************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

module.exports = [
	// Order the groups appear in.
	...__webpack_require__(/*! ./display */ "./src/config/display.js"),
	...__webpack_require__(/*! ./filter */ "./src/config/filter.js"),
	...__webpack_require__(/*! ./keybinds */ "./src/config/keybinds.js"),
	...__webpack_require__(/*! ./templates */ "./src/config/templates.js"),
	...__webpack_require__(/*! ./hosts */ "./src/config/hosts.js"),

	{
		property: 'shuffle',
		default: false
	},
	{
		property: 'repeat',
		default: 'all'
	},
	{
		property: 'viewStyle',
		default: 'playlist'
	},
	{
		property: 'hoverImages',
		default: false
	},
	{
		property: 'showPlaylistSearch',
		default: true
	}
];


/***/ }),

/***/ "./src/config/keybinds.js":
/*!********************************!*\
  !*** ./src/config/keybinds.js ***!
  \********************************/
/*! no static exports found */
/***/ (function(module, exports) {

const hasMediaSession = 'mediaSession' in navigator;

module.exports = [
	{
		property: 'hardwareMediaKeys',
		title: 'Hardware Media Keys',
		displayGroup: 'Keybinds',
		description: 'Enable playback control via hardware media keys.'
			+ (!hasMediaSession ? ' Your browser does not support this feature.' : ''),
		default: hasMediaSession,
		attrs: !hasMediaSession && 'disabled'
	},
	{
		title: 'Keybinds',
		displayGroup: 'Keybinds',
		description: 'Enable keyboard shortcuts.',
		format: 'hotkeys.stringifyKey',
		parse: 'hotkeys.parseKey',
		class: `${ns}-key-input`,
		property: 'hotkey_bindings',
		settings: [
			{
				property: 'hotkeys',
				default: 'open',
				title: 'Enabled',
				format: null,
				parse: null,
				class: null,
				options: {
					always: 'Always',
					open: 'Only with the player open',
					never: 'Never'
				}
			},
			{
				property: 'hotkey_bindings.playPause',
				title: 'Play/Pause',
				keyHandler: 'togglePlay',
				ignoreRepeat: true,
				default: { key: ' ' }
			},
			{
				property: 'hotkey_bindings.previous',
				title: 'Previous',
				keyHandler: () => Player.previous({ force: true }),
				ignoreRepeat: true,
				default: { key: 'arrowleft' }
			},
			{
				property: 'hotkey_bindings.next',
				title: 'Next',
				keyHandler: () => Player.next({ force: true }),
				ignoreRepeat: true,
				default: { key: 'arrowright' }
			},
			{
				property: 'hotkey_bindings.previousGroup',
				title: 'Previous Group',
				keyHandler: () => Player.previous({ force: true, group: true }),
				ignoreRepeat: true,
				default: { shiftKey: true, key: 'arrowleft' }
			},
			{
				property: 'hotkey_bindings.nextGroup',
				title: 'Next Group',
				keyHandler: () => Player.next({ force: true, group: true }),
				ignoreRepeat: true,
				default: { shiftKey: true, key: 'arrowright' }
			},
			{
				property: 'hotkey_bindings.volumeUp',
				title: 'Volume Up',
				keyHandler: 'controls.volumeUp',
				default: { shiftKey: true, key: 'arrowup' }
			},
			{
				property: 'hotkey_bindings.volumeDown',
				title: 'Volume Down',
				keyHandler: 'controls.volumeDown',
				default: { shiftKey: true, key: 'arrowdown' }
			},
			{
				property: 'hotkey_bindings.closePlayer',
				title: 'Close',
				keyHandler: 'display.close',
				default: { key: '' }
			},
			{
				property: 'hotkey_bindings.togglePlayer',
				title: 'Show/Hide',
				keyHandler: 'display.toggle',
				default: { key: 'h' }
			},
			{
				property: 'hotkey_bindings.toggleFullscreen',
				title: 'Toggle Fullscreen',
				keyHandler: 'display.toggleFullScreen',
				default: { key: '' }
			},
			{
				property: 'hotkey_bindings.togglePlaylist',
				title: 'Toggle Playlist',
				keyHandler: 'playlist.toggleView',
				default: { key: '' }
			},
			{
				property: 'hotkey_bindings.toggleSearch',
				title: 'Toggle Playlist Search',
				keyHandler: () => Player.set('showPlaylistSearch', !Player.config.showPlaylistSearch),
				default: { key: '' }
			},
			{
				property: 'hotkey_bindings.scrollToPlaying',
				title: 'Jump To Playing',
				keyHandler: 'playlist.scrollToPlaying',
				default: { key: '' }
			},
			{
				property: 'hotkey_bindings.toggleHoverImages',
				title: 'Toggle Hover Images',
				keyHandler: 'playlist.toggleHoverImages',
				default: { key: '' }
			},
			{
				property: 'hotkey_bindings.toggleAutoScroll',
				title: 'Toggle Auto Scroll',
				keyHandler:  () => Player.set('autoScrollThread', !Player.config.autoScrollThread),
				default: { key: '' }
			}
		]
	}
];


/***/ }),

/***/ "./src/config/templates.js":
/*!*********************************!*\
  !*** ./src/config/templates.js ***!
  \*********************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = [
	{
		property: 'headerTemplate',
		title: 'Header',
		actions: [ { title: 'Reset', handler: 'settings.reset' } ],
		default: 'repeat-button shuffle-button hover-images-button playlist-button\nsound-name-marquee\nview-menu-button add-button reload-button close-button',
		displayGroup: 'Templates',
		displayMethod: 'textarea'
	},
	{
		property: 'rowTemplate',
		title: 'Row',
		actions: [ { title: 'Reset', handler: 'settings.reset' } ],
		default: 'sound-name h:{menu-button}',
		displayGroup: 'Templates',
		displayMethod: 'textarea'
	},
	{
		property: 'footerTemplate',
		title: 'Footer',
		actions: [ { title: 'Reset', handler: 'settings.reset' } ],
		default: 'playing-button:"sound-index /" sound-count sounds\n'
			+ 'p:{\n'
			+ '	<div style="float: right; margin-right: .5rem">\n'
			+ '		post-link\n'
			+ '		Open [ image-link sound-link ]\n'
			+ '		Download [ dl-image-button dl-sound-button ]\n'
			+ '	</div>\n'
			+ '}',
		description: 'Template for the footer contents',
		displayGroup: 'Templates',
		displayMethod: 'textarea',
		attrs: 'style="height:9em;"'
	},
	{
		property: 'chanXTemplate',
		title: '4chan X Header',
		default: 'p:{\n\tpost-link:"sound-name"\n\tprev-button\n\tplay-button\n\tnext-button\n\tsound-current-time / sound-duration\n}',
		actions: [ { title: 'Reset', handler: 'settings.reset' } ],
		displayGroup: 'Templates',
		displayMethod: 'textarea',
		attrs: 'style="height:9em;"'
	}
];


/***/ }),

/***/ "./src/file_parser.js":
/*!****************************!*\
  !*** ./src/file_parser.js ***!
  \****************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(_) {const selectors = __webpack_require__(/*! ./selectors */ "./src/selectors.js");

const protocolRE = /^(https?:)?\/\//;
const filenameRE = /(.*?)[[({](?:audio|sound)[ =:|$](.*?)[\])}]/gi;

let localCounter = 0;

module.exports = {
	parseFiles,
	parsePost,
	parseFileName
};

function parseFiles(target, postRender) {
	let addedSounds = false;
	let posts = target.classList.contains('post')
		? [ target ]
		: target.querySelectorAll(selectors.posts);

	posts.forEach(post => parsePost(post, postRender) && (addedSounds = true));

	if (addedSounds && postRender && Player.container) {
		Player.playlist.render();
	}
}

function parsePost(post, skipRender) {
	try {
		if (post.classList.contains('style-fetcher')) {
			return;
		}
		const parentParent = post.parentElement.parentElement;
		if (parentParent.id === 'qp' || post.parentElement.classList.contains('noFile')) {
			return;
		}

		// If there's a play button this post has already been parsed. Just wire up the link.
		let playLink = post.querySelector(`.${ns}-play-link`);
		if (playLink) {
			const id = playLink.getAttribute('data-id');
			playLink.onclick = () => Player.play(Player.sounds.find(sound => sound.id === id));
			return;
		}

		let filename = null;
		let filenameLocations = selectors.filename;

		Object.keys(filenameLocations).some(function (selector) {
			const node = post.querySelector(selector);
			return node && (filename = node[filenameLocations[selector]]);
		});

		if (!filename) {
			return;
		}

		selectors.filenameParser && (filename = selectors.filenameParser(filename));

		const postID = post.id.slice(selectors.postIdPrefix.length);
		const fileThumb = post.querySelector(selectors.thumb).closest('a');
		const imageSrc = fileThumb && fileThumb.href;
		const thumbImg = fileThumb && fileThumb.querySelector('img');
		const thumbSrc = thumbImg && thumbImg.src;
		const imageMD5 = Site === 'Fuuka'
			? post.querySelector(':scope > a:nth-of-type(3)').href.split('/').pop()
			: thumbImg && thumbImg.getAttribute('data-md5');

		const sounds = parseFileName(filename, imageSrc, postID, thumbSrc, imageMD5);

		if (!sounds.length) {
			return;
		}

		// Create a play link
		const firstID = sounds[0].id;
		const linkInfo = selectors.playLink;
		const content = `<a href="javascript:;" class="${linkInfo.class}" data-id="${firstID}">${linkInfo.text}</a>`;

		const playLinkRelative = linkInfo.relative && post.querySelector(linkInfo.relative);

		linkInfo.prependText && _addPlayLinkText(linkInfo.prependText, linkInfo.before, playLinkRelative);
		playLink = linkInfo.before
			? _.elementBefore(content, playLinkRelative)
			: _.element(content, playLinkRelative);
		linkInfo.appendText && _addPlayLinkText(linkInfo.appendText, linkInfo.before, playLinkRelative);
		playLink.onclick = () => Player.play(sounds[0]);

		// Don't add sounds from inline quotes of posts in the thread
		sounds.forEach(sound => Player.add(sound, skipRender));
		return sounds.length > 0;
	} catch (err) {
		Player.logError('There was an issue parsing the files. Please check the console for details.', err);
		console.log('[4chan sounds player]', post);
	}
}

function _addPlayLinkText(text, before, relative) {
	const node = text && document.createTextNode(text);
	if (before) {
		relative.parentNode.insertBefore(node, relative);
	} else {
		relative.appendChild(node);
	}
}

function parseFileName(filename, image, post, thumb, imageMD5, bypassVerification) {
	if (!filename) {
		return [];
	}
	filename = filename.replace(/-/, '/');
	const matches = [];
	let match;
	while ((match = filenameRE.exec(filename)) !== null) {
		matches.push(match);
	}
	// Add webms without a sound filename as a standable video if enabled
	if (!matches.length && (Player.config.addWebm === 'always' || (Player.config.addWebm === 'soundBoards' && (Board === 'gif' || Board === 'wsg'))) && filename.endsWith('.webm')) {
		matches.push([ null, filename.slice(0, -5), image ]);
	}
	const defaultName = matches[0] && matches[0][1] || post || 'Local Sound ' + localCounter;
	matches.length && !post && localCounter++;

	return matches.reduce((sounds, match, i) => {
		let src = match[2];
		const id = (post || 'local' + localCounter) + ':' + i;
		const name = match[1].trim();
		const title = name || defaultName + (matches.length > 1 ? ` (${i + 1})` : '');
		const standaloneVideo = src === image;

		try {
			if (src.includes('%')) {
				src = decodeURIComponent(src);
			}

			if (!src.startsWith('blob:') && src.match(protocolRE) === null) {
				src = (location.protocol + '//' + src);
			}
		} catch (error) {
			return sounds;
		}

		const sound = { src, id, title, name, post, image, filename, thumb, imageMD5, standaloneVideo };
		if (bypassVerification || Player.acceptedSound(sound)) {
			sounds.push(sound);
		}
		return sounds;
	}, []);
}

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/_ */ "./src/_.js")))

/***/ }),

/***/ "./src/globals.js":
/*!************************!*\
  !*** ./src/globals.js ***!
  \************************/
/*! no static exports found */
/***/ (function(module, exports) {

/**
 * Global variables and helpers.
 */

window.ns = 'fc-sounds';

window.is4chan = location.hostname.includes('4chan.org') || location.hostname.includes('4channel.org');
window.isChanX = document.documentElement && document.documentElement.classList.contains('fourchan-x');
window.Board = location.pathname.split('/')[1];

// Determine what type of site this is. Default to FoolFuuka as the most common archiver.
window.Site = is4chan ? '4chan'
	: ((document.head.querySelector('meta[name="generator"]') || {}).content || '').includes('FoolFuuka') ? 'FoolFuuka'
	: ((document.head.querySelector('meta[name="description"]') || {}).content || '').includes('Fuuka') ? 'Fuuka'
	: 'FoolFuuka';

class PlayerError extends Error {
	constructor(msg, type, err) {
		super(msg);
		this.reason = msg;
		this.type = type;
		this.error = err;
	}
}
window.PlayerError = PlayerError;


/***/ }),

/***/ "./src/icons.js":
/*!**********************!*\
  !*** ./src/icons.js ***!
  \**********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

module.exports = {
	fcSounds: '<svg width="452" height="257" xmlns="http://www.w3.org/2000/svg"><g><text font-weight="bold" font-style="italic" font-family="Helvetica, Arial, sans-serif" font-size="250" y="197" fill-opacity="0.05" fill="#000000">4sp</text></g></svg>',
	arrowClockwise: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/arrow-clockwise.svg */ "./node_modules/bootstrap-icons/icons/arrow-clockwise.svg").default,
	arrowsCollapse: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/arrows-collapse.svg */ "./node_modules/bootstrap-icons/icons/arrows-collapse.svg").default,
	arrowsExpand: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/arrows-expand.svg */ "./node_modules/bootstrap-icons/icons/arrows-expand.svg").default,
	arrowRepeat: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/arrow-repeat.svg */ "./node_modules/bootstrap-icons/icons/arrow-repeat.svg").default,
	chatRightQuote: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/chat-right-quote.svg */ "./node_modules/bootstrap-icons/icons/chat-right-quote.svg").default,
	chevronDown: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/chevron-down.svg */ "./node_modules/bootstrap-icons/icons/chevron-down.svg").default,
	close: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/plus.svg */ "./node_modules/bootstrap-icons/icons/plus.svg").default.replace('bi-plus', `bi-plus ${ns}-icon-close`).replace(/viewBox="[^"]+"/, 'viewBox="2 2 12 12"'),
	gear: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/gear.svg */ "./node_modules/bootstrap-icons/icons/gear.svg").default,
	fileEarmarkImage: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/file-earmark-image.svg */ "./node_modules/bootstrap-icons/icons/file-earmark-image.svg").default,
	fileEarmarkMusic: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/file-earmark-music.svg */ "./node_modules/bootstrap-icons/icons/file-earmark-music.svg").default,
	filter: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/filter.svg */ "./node_modules/bootstrap-icons/icons/filter.svg").default,
	fullscreen: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/fullscreen.svg */ "./node_modules/bootstrap-icons/icons/fullscreen.svg").default,
	fullscreenExit: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/fullscreen-exit.svg */ "./node_modules/bootstrap-icons/icons/fullscreen-exit.svg").default,
	image: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/image.svg */ "./node_modules/bootstrap-icons/icons/image.svg").default,
	link: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/link-45deg.svg */ "./node_modules/bootstrap-icons/icons/link-45deg.svg").default,
	musicNoteList: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/music-note-list.svg */ "./node_modules/bootstrap-icons/icons/music-note-list.svg").default,
	play: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/play.svg */ "./node_modules/bootstrap-icons/icons/play.svg").default.replace(/viewBox="[^"]+"/, 'viewBox="2 2 12 12"'),
	playFill: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/play-fill.svg */ "./node_modules/bootstrap-icons/icons/play-fill.svg").default.replace(/viewBox="[^"]+"/, 'viewBox="2 2 12 12"'),
	pause: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/pause.svg */ "./node_modules/bootstrap-icons/icons/pause.svg").default.replace(/viewBox="[^"]+"/, 'viewBox="2 2 12 12"'),
	pauseFill: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/pause-fill.svg */ "./node_modules/bootstrap-icons/icons/pause-fill.svg").default.replace(/viewBox="[^"]+"/, 'viewBox="2 2 12 12"'),
	plus: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/plus-circle.svg */ "./node_modules/bootstrap-icons/icons/plus-circle.svg").default,
	reboot: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/bootstrap-reboot.svg */ "./node_modules/bootstrap-icons/icons/bootstrap-reboot.svg").default,
	search: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/search.svg */ "./node_modules/bootstrap-icons/icons/search.svg").default,
	shuffle: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/shuffle.svg */ "./node_modules/bootstrap-icons/icons/shuffle.svg").default,
	skipEnd: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/skip-end.svg */ "./node_modules/bootstrap-icons/icons/skip-end.svg").default.replace(/viewBox="[^"]+"/, 'viewBox="2 2 12 12"'),
	skipEndFill: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/skip-end-fill.svg */ "./node_modules/bootstrap-icons/icons/skip-end-fill.svg").default.replace(/viewBox="[^"]+"/, 'viewBox="2 2 12 12"'),
	skipStart: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/skip-start.svg */ "./node_modules/bootstrap-icons/icons/skip-start.svg").default.replace(/viewBox="[^"]+"/, 'viewBox="2 2 12 12"'),
	skipStartFill: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/skip-start-fill.svg */ "./node_modules/bootstrap-icons/icons/skip-start-fill.svg").default.replace(/viewBox="[^"]+"/, 'viewBox="2 2 12 12"'),
	soundwave: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/soundwave.svg */ "./node_modules/bootstrap-icons/icons/soundwave.svg").default,
	tools: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/tools.svg */ "./node_modules/bootstrap-icons/icons/tools.svg").default,
	trash: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/trash.svg */ "./node_modules/bootstrap-icons/icons/trash.svg").default,
	volumeMute: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/volume-mute.svg */ "./node_modules/bootstrap-icons/icons/volume-mute.svg").default.replace(/viewBox="[^"]+"/, 'viewBox="1 1 14 14"'),
	volumeMuteFill: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/volume-mute-fill.svg */ "./node_modules/bootstrap-icons/icons/volume-mute-fill.svg").default.replace(/viewBox="[^"]+"/, 'viewBox="1 1 14 14"'),
	volumeUp: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/volume-up.svg */ "./node_modules/bootstrap-icons/icons/volume-up.svg").default.replace(/viewBox="[^"]+"/, 'viewBox="1 1 14 14"'),
	volumeUpFill: __webpack_require__(/*! ../node_modules/bootstrap-icons/icons/volume-up-fill.svg */ "./node_modules/bootstrap-icons/icons/volume-up-fill.svg").default.replace(/viewBox="[^"]+"/, 'viewBox="1 1 14 14"')
};


/***/ }),

/***/ "./src/main.js":
/*!*********************!*\
  !*** ./src/main.js ***!
  \*********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


async function doInit() {
	// Require globals again here just in case 4chan X loaded before timeout below.
	__webpack_require__(/*! ./globals */ "./src/globals.js");

	// Require these here so every other require is sure of the 4chan X state.
	const Player = __webpack_require__(/*! ./player */ "./src/player.js");
	const { parseFiles } = __webpack_require__(/*! ./file_parser */ "./src/file_parser.js");

	await Player.initialize();

	parseFiles(document.body, true);

	const observer = new MutationObserver(function (mutations) {
		mutations.forEach(function (mutation) {
			if (mutation.type === 'childList') {
				mutation.addedNodes.forEach(function (node) {
					if (node.nodeType === Node.ELEMENT_NODE) {
						parseFiles(node);
					}
				});
			}
		});
	});

	observer.observe(document.body, {
		childList: true,
		subtree: true
	});
}

document.addEventListener('4chanXInitFinished', doInit);

// The timeout makes sure 4chan X will have added it's classes and be identified.
// The player also tends to be all black without a timeout.
// Something with the timing of the stylesheet loading and applying the board theme.
setTimeout(function () {
	__webpack_require__(/*! ./globals */ "./src/globals.js");

	// If it's already known 4chan X is installed this can be skipped.
	if (!isChanX) {
		if (document.readyState !== 'loading') {
			doInit();
		} else {
			document.addEventListener('DOMContentLoaded', doInit);
		}
	}
}, 0);



/***/ }),

/***/ "./src/migrations.js":
/*!***************************!*\
  !*** ./src/migrations.js ***!
  \***************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = [
	{
		version: '3.0.0',
		name: 'hosts-filename-length',
		async run() {
			const defaultHosts = Player.settings.findDefault('uploadHosts').default;
			Object.keys(defaultHosts).forEach(host => {
				Player.config.uploadHosts[host].filenameLength = defaultHosts[host].filenameLength;
			});
		}
	}
];


/***/ }),

/***/ "./src/player.js":
/*!***********************!*\
  !*** ./src/player.js ***!
  \***********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(_) {const components = {
	// Settings must be first.
	settings: __webpack_require__(/*! ./components/settings */ "./src/components/settings.js"),
	events: __webpack_require__(/*! ./components/events */ "./src/components/events.js"),
	actions: __webpack_require__(/*! ./components/actions */ "./src/components/actions.js"),
	colorpicker: __webpack_require__(/*! ./components/colorpicker */ "./src/components/colorpicker.js"),
	controls: __webpack_require__(/*! ./components/controls */ "./src/components/controls.js"),
	display: __webpack_require__(/*! ./components/display */ "./src/components/display.js"),
	footer: __webpack_require__(/*! ./components/footer */ "./src/components/footer.js"),
	header: __webpack_require__(/*! ./components/header */ "./src/components/header.js"),
	hotkeys: __webpack_require__(/*! ./components/hotkeys */ "./src/components/hotkeys.js"),
	minimised: __webpack_require__(/*! ./components/minimised */ "./src/components/minimised.js"),
	playlist: __webpack_require__(/*! ./components/playlist */ "./src/components/playlist.js"),
	position: __webpack_require__(/*! ./components/position */ "./src/components/position.js"),
	threads: __webpack_require__(/*! ./components/threads */ "./src/components/threads.js"),
	tools: __webpack_require__(/*! ./components/tools */ "./src/components/tools.js"),
	userTemplate: __webpack_require__(/*! ./components/user-template */ "./src/components/user-template/index.js")
};

// Create a global ref to the player.
const Player = window.Player = module.exports = {
	ns,

	audio: new Audio(),
	sounds: [],
	isHidden: true,
	container: null,
	ui: {},
	_public: [],

	// Build the config from the default
	config: {},

	// Helper function to query elements in the player.
	$: (...args) => Player.container && Player.container.querySelector(...args),
	$all: (...args) => Player.container && Player.container.querySelectorAll(...args),

	// Store a ref to the components so they can be iterated.
	components,

	// Get all the templates.
	templates: {
		body: __webpack_require__(/*! ./templates/body.tpl */ "./src/templates/body.tpl"),
		colorpicker: __webpack_require__(/*! ./templates/colorpicker.tpl */ "./src/templates/colorpicker.tpl"),
		controls: __webpack_require__(/*! ./templates/controls.tpl */ "./src/templates/controls.tpl"),
		css: __webpack_require__(/*! ./scss/style.scss */ "./src/scss/style.scss"),
		css4chanXPolyfill: __webpack_require__(/*! ./scss/4chan-x-polyfill.scss */ "./src/scss/4chan-x-polyfill.scss"),
		footer: __webpack_require__(/*! ./templates/footer.tpl */ "./src/templates/footer.tpl"),
		header: __webpack_require__(/*! ./templates/header.tpl */ "./src/templates/header.tpl"),
		hostInput: __webpack_require__(/*! ./templates/host_input.tpl */ "./src/templates/host_input.tpl"),
		itemMenu: __webpack_require__(/*! ./templates/item_menu.tpl */ "./src/templates/item_menu.tpl"),
		list: __webpack_require__(/*! ./templates/list.tpl */ "./src/templates/list.tpl"),
		player: __webpack_require__(/*! ./templates/player.tpl */ "./src/templates/player.tpl"),
		settings: __webpack_require__(/*! ./templates/settings.tpl */ "./src/templates/settings.tpl"),
		threads: __webpack_require__(/*! ./templates/threads.tpl */ "./src/templates/threads.tpl"),
		threadBoards: __webpack_require__(/*! ./templates/thread_boards.tpl */ "./src/templates/thread_boards.tpl"),
		threadList: __webpack_require__(/*! ./templates/thread_list.tpl */ "./src/templates/thread_list.tpl"),
		tools: __webpack_require__(/*! ./templates/tools.tpl */ "./src/templates/tools.tpl"),
		viewsMenu: __webpack_require__(/*! ./templates/views_menu.tpl */ "./src/templates/views_menu.tpl")
	},

	/**
	 * Set up the player.
	 */
	initialize: async function initialize() {
		if (Player.initialized) {
			return;
		}
		Player.initialized = true;
		try {
			Player.sounds = [ ];
			// Run the initialisation for each component.
			for (let name in components) {
				components[name].initialize && await components[name].initialize();
			}

			// Show a button to open the player.
			Player.display.createPlayerButton();

			// Render the player, but not neccessarily show it.
			Player.display.render();

			// Expose some functionality via PlayerEvent custom events.
			document.addEventListener('PlayerEvent', e => {
				if (e.detail.action && ( true || false)) {
					return _.get(Player, e.detail.action).apply(window, e.detail.arguments);
				}
			});
		} catch (err) {
			Player.logError('There was an error initialzing the sound player. Please check the console for details.', err);
			// Can't recover so throw this error.
			throw err;
		}
	},

	/**
	 * Returns the function of Player referenced by name or a given handler function.
	 * @param {String|Function} handler Name to function on Player or a handler function.
	 */
	getHandler: function (handler) {
		return typeof handler === 'string' ? _.get(Player, handler) : handler;
	},

	/**
	 * Compare two ids for sorting.
	 */
	compareIds: function (a, b) {
		const [ aPID, aSID ] = a.split(':');
		const [ bPID, bSID ] = b.split(':');
		const postDiff = aPID - bPID;
		return postDiff !== 0 ? postDiff : aSID - bSID;
	},

	/**
	 * Check whether a sound src and image are allowed and not filtered.
	 */
	acceptedSound: function ({ src, imageMD5 }) {
		try {
			const link = new URL(src);
			const host = link.hostname.toLowerCase();
			return !Player.config.filters.find(v => v === imageMD5 || v === host + link.pathname)
				&& Player.config.allow.find(h => host === h || host.endsWith('.' + h));
		} catch (err) {
			return false;
		}
	},

	/**
	 * Listen for changes
	 */
	syncTab: (property, callback) => typeof GM_addValueChangeListener !== 'undefined' && GM_addValueChangeListener(property, (_prop, oldValue, newValue, remote) => {
		remote && callback(newValue, oldValue);
	}),

	/**
	 * Log errors and show an error notification.
	 */
	logError: function (message, error, type) {
		console.error('[4chan sounds player]', message, error);
		if (error instanceof PlayerError) {
			error.error && console.error('[4chan sound player]', error.error);
			message = error.reason;
			type = error.type || type;
		}
		Player.alert(message, type || 'error', 5);
	},

	/**
	 * Show a notification using 4chan X or the native extention.
	 */
	alert: function (content, type = 'info', lifetime = 5) {
		if (isChanX) {
			content = _.element(`<span>${content}</span`);
			document.dispatchEvent(new CustomEvent('CreateNotification', {
				bubbles: true,
				detail: { content, type, lifetime }
			}));
		} else if (typeof Feedback !== 'undefined') {
			Feedback.showMessage(content, type === 'info' ? 'notify' : 'error', lifetime * 1000);
		}
	}
};

// Add each of the components to the player.
for (let name in components) {
	Player[name] = components[name];
	(Player[name].atRoot || []).forEach(k => Player[k] = Player[name][k]);
	(Player[name].public || []).forEach(k => {
		Player._public.push((Player[name].atRoot || []).includes(k) ? k : `${name}.${k}`);
	});
}

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/_ */ "./src/_.js")))

/***/ }),

/***/ "./src/scss/4chan-x-polyfill.scss":
/*!****************************************!*\
  !*** ./src/scss/4chan-x-polyfill.scss ***!
  \****************************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = (data = {}) => `.dialog {
  background: ${Player.config.colors.background};
  background: ${Player.config.colors.background};
  border-color: ${Player.config.colors.border};
  border-radius: 3px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
  border-radius: 3px;
  padding-top: 1px;
  padding-bottom: 3px;
}

.entry {
  position: relative;
  display: block;
  padding: 0.125rem 0.5rem;
  min-width: 70px;
  white-space: nowrap;
}
.entry.has-submenu::after {
  content: "";
  border-left: 0.5em solid;
  border-top: 0.3em solid transparent;
  border-bottom: 0.3em solid transparent;
  display: inline-block;
  margin: 0.35em;
  position: absolute;
  right: 3px;
}
.entry.focused {
  background: rgba(255, 255, 255, 0.33);
}
.entry.focused > .submenu {
  display: block;
}

.submenu {
  position: absolute;
  display: none;
}`

/***/ }),

/***/ "./src/scss/style.scss":
/*!*****************************!*\
  !*** ./src/scss/style.scss ***!
  \*****************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = (data = {}) => `.${ns}-colorpicker {
  position: fixed;
  padding: 0.25rem;
  white-space: nowrap;
  z-index: 999;
}
.${ns}-colorpicker .${ns}-cp-saturation {
  display: inline-block;
  position: relative;
}
.${ns}-colorpicker .${ns}-cp-saturation .position {
  width: 5px;
}
.${ns}-colorpicker .${ns}-cp-saturation::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  border-radius: inherit;
  background: black;
  -webkit-mask-image: linear-gradient(#0000, #000);
  mask-image: linear-gradient(#0000, #000);
}
.${ns}-colorpicker .${ns}-cp-hue {
  margin-left: 0.5rem;
  display: inline-block;
  position: relative;
  width: 30px;
  background: linear-gradient(to bottom, #F00, #FF0, #0F0, #0FF, #00F, #F0F, #F00);
}
.${ns}-colorpicker .${ns}-cp-hue .position {
  top: -3px;
  left: -1px;
  right: -1px;
}
.${ns}-colorpicker .${ns}-cp-saturation .position, .${ns}-colorpicker .${ns}-cp-hue .position {
  position: absolute;
  height: 5px;
  border-radius: 1rem;
}
.${ns}-colorpicker .${ns}-output {
  vertical-align: top;
  margin-left: 1rem;
  display: inline-block;
}
.${ns}-colorpicker .${ns}-output .${ns}-rgb-input {
  width: 2rem;
}
.${ns}-colorpicker .${ns}-output .output-color {
  height: 40px;
  margin: 0.25rem 0;
}

.${ns}-cp-preview, .${ns}-cp-saturation .position, .${ns}-cp-hue .position, .${ns}-output .output-color {
  border: solid 1px black;
  box-shadow: inset 0 0 0 1px #EEE;
}

.${ns}-cp-preview {
  height: 1em;
  width: 1em;
  margin-left: 0.125rem;
}

.${ns}-text-muted {
  color: #909090;
}

.${ns}-controls {
  align-items: center;
  padding: 0.5rem 0;
  position: relative;
  background: ${Player.config.colors.controls_background};
  justify-content: space-between;
}
.${ns}-controls > div {
  margin: 0 0.5rem;
}
.${ns}-controls .${ns}-current-time {
  color: ${Player.config.colors.controls_inactive};
}
.${ns}-controls .${ns}-media-control {
  width: 1.5rem;
  height: 1.5rem;
  font-size: 1rem;
  color: ${Player.config.colors.controls_inactive};
}
.${ns}-controls .${ns}-media-control:hover {
  color: ${Player.config.colors.controls_active};
}

.${ns}-media-control {
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
}
.${ns}-media-control.${ns}-hover-fill svg[class$=-fill], .${ns}-media-control.${ns}-hover-fill svg[class*="-fill "] {
  display: none;
}
.${ns}-media-control.${ns}-hover-fill:hover svg {
  display: none;
}
.${ns}-media-control.${ns}-hover-fill:hover svg[class$=-fill], .${ns}-media-control.${ns}-hover-fill:hover svg[class*="-fill "] {
  display: block;
}
.${ns}-media-control.${ns}-play-button:not(.${ns}-play) svg.bi-play-fill, .${ns}-media-control.${ns}-play-button:not(.${ns}-play) svg.bi-play {
  display: none !important;
}
.${ns}-media-control.${ns}-play-button.${ns}-play svg.bi-pause-fill, .${ns}-media-control.${ns}-play-button.${ns}-play svg.bi-pause {
  display: none !important;
}
.${ns}-media-control.${ns}-fullscreen-button svg.bi-fullscreen-exit {
  display: none;
}
#${ns}-container[data-view-style=fullscreen] .${ns}-media-control.${ns}-fullscreen-button svg.bi-fullscreen-exit {
  display: block;
}
#${ns}-container[data-view-style=fullscreen] .${ns}-media-control.${ns}-fullscreen-button svg.bi-fullscreen {
  display: none;
}
.${ns}-media-control.${ns}-volume-button.mute .bi:not(.bi-volume-mute):not(.bi-volume-mute-fill) {
  display: none;
}
.${ns}-media-control.${ns}-volume-button.up .bi:not(.bi-volume-up):not(.bi-volume-up-fill) {
  display: none;
}

.${ns}-progress-bar {
  min-width: 3.5rem;
  height: 1.5rem;
  display: flex;
  align-items: center;
}
.${ns}-progress-bar .${ns}-full-bar {
  height: 0.3rem;
  width: 100%;
  background: ${Player.config.colors.controls_empty_bar};
  border-radius: 1rem;
  position: relative;
}
.${ns}-progress-bar .${ns}-full-bar > div {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 1rem;
}
.${ns}-progress-bar .${ns}-full-bar .${ns}-loaded-bar {
  background: ${Player.config.colors.controls_loaded_bar};
}
.${ns}-progress-bar .${ns}-full-bar .${ns}-current-bar {
  display: flex;
  justify-content: flex-end;
  align-items: center;
}
.${ns}-progress-bar .${ns}-full-bar .${ns}-current-bar:after {
  content: "";
  background: ${Player.config.colors.controls_inactive};
  height: 0.8rem;
  min-width: 0.8rem;
  border-radius: 1rem;
  box-shadow: rgba(0, 0, 0, 0.76) 0 0 3px 0;
  margin-right: -0.4rem;
}
.${ns}-progress-bar:hover .${ns}-current-bar:after {
  background: ${Player.config.colors.controls_active};
}

.${ns}-seek-bar .${ns}-current-bar {
  background: ${Player.config.colors.controls_active};
}

.${ns}-volume-bar .${ns}-current-bar {
  background: ${Player.config.colors.controls_inactive};
}

.${ns}-chan-x-controls .${ns}-current-time, .${ns}-chan-x-controls .${ns}-duration {
  margin: 0 0.25rem;
}

.${ns}-footer {
  padding: 0.15rem 0.25rem;
  border-top: solid 1px ${Player.config.colors.border};
}
.${ns}-footer .${ns}-expander {
  position: absolute;
  bottom: 0px;
  right: 0px;
  height: 0.75rem;
  width: 0.75rem;
  cursor: se-resize;
  background: linear-gradient(to bottom right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0) 50%, ${Player.config.colors.border} 55%, ${Player.config.colors.border} 100%);
}
.${ns}-footer:hover .${ns}-hover-display {
  display: inline-block;
}

.${ns}-header {
  cursor: grab;
  text-align: center;
  border-bottom: solid 1px ${Player.config.colors.border};
  padding: 0.125rem;
}
.${ns}-header:hover .${ns}-hover-display {
  display: flex;
}

.${ns}-title-marquee {
  transition: margin-left 1s linear;
}

.${ns}-icon-close {
  transform: rotate(45deg);
}

.${ns}-footer svg.bi {
  margin-bottom: -0.2rem;
}

.${ns}-menu svg.bi {
  margin: 0 -0.25rem 0 0.25rem;
}

.${ns}-header svg.bi {
  margin: 0 0.125rem;
}

.muted svg.bi {
  opacity: 0.45;
}

svg.bi {
  overflow: visible;
}

.${ns}-image-link {
  text-align: center;
  display: flex;
  justify-items: center;
  justify-content: center;
}
.${ns}-image-link.${ns}-pip {
  position: fixed;
  right: 10px;
  height: ${Player.config.maxPIPWidth} !important;
  max-width: ${Player.config.maxPIPWidth};
  align-items: end;
}
.${ns}-image-link.${ns}-pip .${ns}-image, .${ns}-image-link.${ns}-pip .${ns}-video {
  max-height: 100%;
  height: initial;
  width: initial;
  object-fit: contain;
}

.${ns}-image-link .${ns}-video {
  display: none;
}

.${ns}-image, .${ns}-video {
  height: 100%;
  width: 100%;
  object-fit: contain;
}

.${ns}-image-link.${ns}-show-video .${ns}-video {
  display: block;
}
.${ns}-image-link.${ns}-show-video .${ns}-image {
  display: none;
}

#${ns}-container {
  position: fixed;
  background: ${Player.config.colors.background};
  border: 1px solid ${Player.config.colors.border};
  min-width: 7rem;
  color: ${Player.config.colors.text};
}

.${ns}-panel {
  padding: 0 0.25rem;
  height: 100%;
  width: calc(100% - .5rem);
  overflow: auto;
}

.${ns}-heading {
  font-weight: 600;
  margin: 0.5rem 0;
}

.${ns}-has-description {
  cursor: help;
}

.${ns}-heading-action {
  font-weight: normal;
  text-decoration: underline;
  margin-left: 0.25rem;
}

.${ns}-row {
  display: flex;
  flex-wrap: wrap;
  min-width: 100%;
  box-sizing: border-box;
}
.${ns}-row.nowrap {
  flex-wrap: nowrap;
}

.${ns}-col-auto {
  flex: 0 0 auto;
  width: auto;
  max-width: 100%;
  display: inline-flex;
}

.${ns}-col {
  flex-basis: 0;
  flex-grow: 1;
  max-width: 100%;
  width: 100%;
  position: relative;
}

.${ns}-align-center {
  align-items: center;
  align-content: center;
  align-self: center;
}

.${ns}-align-start {
  align-items: start;
  align-content: start;
  align-self: start;
}

.${ns}-space-between {
  justify-content: space-between;
}

html.fourchan-x #${ns}-container .fa {
  font-size: 0;
  visibility: hidden;
  margin: 0 0.15rem;
}

.${ns}-truncate-text {
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.${ns}-hover-display {
  display: none;
}

.${ns}-player .${ns}-hover-image {
  position: fixed;
  max-height: 125px;
  max-width: 125px;
}
.${ns}-player.${ns}-hide-hover-image .${ns}-hover-image {
  display: none !important;
}

.${ns}-list-container {
  overflow-y: auto;
}
.${ns}-list-container .${ns}-list-item {
  list-style-type: none;
  padding: 0.15rem 0.25rem;
  white-space: nowrap;
  text-overflow: ellipsis;
  cursor: pointer;
  background: ${Player.config.colors.odd_row};
  overflow: hidden;
  height: 1.3rem;
}
.${ns}-list-container .${ns}-list-item.playing {
  background: ${Player.config.colors.playing} !important;
}
.${ns}-list-container .${ns}-list-item:nth-child(2n) {
  background: ${Player.config.colors.even_row};
}
.${ns}-list-container .${ns}-list-item .${ns}-item-menu-button {
  right: 0.25rem;
}
.${ns}-list-container .${ns}-list-item:hover .${ns}-hover-display {
  display: flex;
}
.${ns}-list-container .${ns}-list-item.${ns}-dragging {
  background: ${Player.config.colors.dragging};
}

.${ns}-settings textarea {
  border: solid 1px ${Player.config.colors.border};
  min-width: 100%;
  min-height: 4rem;
  box-sizing: border-box;
  white-space: pre;
}
.${ns}-settings .${ns}-sub-settings .${ns}-col {
  min-height: 1.55rem;
  display: flex;
  align-items: center;
  align-content: center;
  white-space: nowrap;
}
.${ns}-settings .${ns}-settings-tabs {
  align-items: center;
  align-content: center;
  justify-content: center;
}
.${ns}-settings .${ns}-settings-tab {
  margin: 0.25rem;
  text-decoration: underline;
  text-align: center;
}
.${ns}-settings .${ns}-settings-tab.active {
  font-weight: bold;
}
.${ns}-settings .${ns}-settings-group {
  display: none;
}
.${ns}-settings .${ns}-settings-group.active {
  display: block;
}
.${ns}-settings .${ns}-host-input {
  margin: 0.5rem 0;
  border-top: solid 1px ${Player.config.colors.border};
}
.${ns}-settings .${ns}-host-input.invalid {
  border: solid 1px red;
}
.${ns}-settings .${ns}-host-input .${ns}-host-controls {
  align-items: center;
  justify-content: space-between;
  margin: 0.125rem 0;
}
.${ns}-settings .${ns}-host-input input[type=text] {
  min-width: 100%;
  box-sizing: border-box;
}

.${ns}-threads .${ns}-thread-board-list label {
  display: inline-block;
  width: 4rem;
}
.${ns}-threads .${ns}-thread-list {
  margin: 0.5rem -0.25rem 0;
  padding: 0.5rem 1rem;
  border-top: solid 1px ${Player.config.colors.border};
}
.${ns}-threads .${ns}-thread-list .boardBanner {
  margin: 1rem 0;
}
.${ns}-threads table {
  border-top: solid 1px ${Player.config.colors.border};
  width: 100%;
  margin-top: 0.5rem;
  border-collapse: collapse;
}
.${ns}-threads table th {
  border-bottom: solid 1px ${Player.config.colors.border};
}
.${ns}-threads table th, .${ns}-threads table td {
  text-align: left;
  padding: 0.25rem;
}
.${ns}-threads table tr {
  padding: 0.25rem 0;
}
.${ns}-threads table .${ns}-threads-body tr {
  background: ${Player.config.colors.even_row};
}
.${ns}-threads table .${ns}-threads-body tr:nth-child(2n) {
  background: ${Player.config.colors.odd_row};
}

.${ns}-create-sound-status {
  margin-top: 0.5rem;
  border: solid 1px ${Player.config.colors.border};
  border-radius: 5px;
  padding: 0.25rem;
}

.${ns}-file-input, .${ns}-tools input[type=text] {
  width: 100%;
  color: black;
}

.${ns}-file-overlay, .${ns}-tools input[type=text] {
  box-sizing: border-box;
  height: 1.5rem;
  border: solid 1px ${Player.config.colors.border};
  min-width: 5rem;
  display: flex;
  align-items: center;
  padding: 0 0.25rem;
  margin: 0;
}

.${ns}-file-input.placeholder span, .${ns}-create-sound-form input[type=text]::placeholder {
  color: #AAA;
  opacity: 1;
}

.${ns}-file-input .${ns}-file-overlay {
  position: relative;
  background: white;
}
.${ns}-file-input .placeholder-text {
  display: none;
}
.${ns}-file-input.placeholder .placeholder-text {
  display: inherit;
}
.${ns}-file-input span {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.${ns}-file-input input[type=file] {
  width: 100%;
  box-sizing: border-box;
  height: 100%;
  position: absolute;
  left: 0;
  opacity: 0;
}
.${ns}-file-input .overfile {
  z-index: 9999;
}
.${ns}-file-input .${ns}-file-list {
  padding: 0 0.25rem;
}
.${ns}-file-input .${ns}-file-list:empty {
  display: none;
}

.${ns}-input-append {
  position: absolute;
  display: flex;
  align-items: center;
  background: white;
  padding-left: 0.25rem;
  right: 0.125rem;
}

.${ns}-threads, .${ns}-settings, .${ns}-tools, .${ns}-player {
  display: none;
}

#${ns}-container[data-view-style=settings] .${ns}-settings {
  display: block;
}

#${ns}-container[data-view-style=threads] .${ns}-threads {
  display: block;
}

#${ns}-container[data-view-style=tools] .${ns}-tools {
  display: block;
}

#${ns}-container[data-view-style=image] .${ns}-player,
#${ns}-container[data-view-style=playlist] .${ns}-player,
#${ns}-container[data-view-style=fullscreen] .${ns}-player {
  display: block;
}

#${ns}-container[data-view-style=image] .${ns}-list-container, #${ns}-container[data-view-style=image] .${ns}-playlist-search {
  display: none;
}
#${ns}-container[data-view-style=image] .${ns}-image-link {
  height: auto;
}

#${ns}-container[data-view-style=playlist] .${ns}-image-link {
  height: 125px !important;
}

#${ns}-container[data-view-style=fullscreen] .${ns}-image-link {
  height: calc(100% - .4rem) !important;
}
#${ns}-container[data-view-style=fullscreen] .${ns}-controls {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(-2.5rem + .4rem);
  opacity: 0.7;
}
#${ns}-container[data-view-style=fullscreen] .${ns}-controls:hover {
  bottom: 0;
}`

/***/ }),

/***/ "./src/selectors.js":
/*!**************************!*\
  !*** ./src/selectors.js ***!
  \**************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = {
	'4chan': {
		postIdPrefix: 'p',
		posts: '.post',
		// For 4chan there's native / 4chan X / 4chan X with file info formatting
		filename: {
			'.fileText .file-info .fnfull': 'textContent',
			'.fileText .file-info > a': 'textContent',
			'.fileText > a': 'title',
			'.fileText': 'textContent'
		},
		thumb: '.fileThumb',
		playLink: {
			class: `${ns}-play-link`,
			text: 'play',
			relative: '.fileText',
			prependText: ' '
		},
		// Deliberately missing dots because this is used to set the class
		styleFetcher: 'post reply style-fetcher',
		limitWidthOf: '.thread > .postContainer'
	},
	FoolFuuka: {
		postIdPrefix: '',
		posts: 'article',
		// For the archive the OP and reply selector differs
		filename: {
			'.thread_image_box .post_file_filename': 'textContent',
			'.post_file_filename': 'title'
		},
		thumb: '.thread_image_link',
		playLink: {
			class: `${ns}-play-link btnr`,
			text: 'Play',
			relative: '.post_controls'
		},
		styleFetcher: 'post_wrapper style-fetcher',
		limitWidthOf: '.posts > article.post'
	},
	Fuuka: {
		postIdPrefix: 'p',
		posts: '.content > div, td.reply',
		filename: {
			':scope > span': 'textContent'
		},
		filenameParser: v => v.split(', ').slice(2).join(', '),
		thumb: '.thumb',
		playLink: {
			class: `${ns}-play-link`,
			text: 'play',
			relative: 'br:nth-of-type(2)',
			before: true,
			prependText: ' [',
			appendText: ']'
		},
		styleFetcher: 'reply style-fetcher',
		limitWidthOf: '.content > div, .content > table'
	}
}[Site];


/***/ }),

/***/ "./src/templates/body.tpl":
/*!********************************!*\
  !*** ./src/templates/body.tpl ***!
  \********************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = (data = {}) => `<div id="${ns}-container" data-view-style="${Player.config.viewStyle}" style="top: 30px; left: 0px; width: 360px; display: none;">
	<div class="${ns}-header ${ns}-row ${ns}-align-center">
		${Player.templates.header(data)}
	</div>
	<div class="${ns}-view-container">
		<div class="${ns}-player ${!Player.config.hoverImages ? `${ns}-hide-hover-image` : ''}">
			${Player.templates.player(data)}
		</div>
		<div class="${ns}-settings ${ns}-panel" style="height: 400px">
			${Player.templates.settings(data)}
		</div>
		<div class="${ns}-threads ${ns}-panel" style="height: 400px">
			${Player.templates.threads(data)}
		</div>
		<div class="${ns}-tools ${ns}-panel" style="height: 400px">
			${Player.templates.tools(data)}
		</div>
	</div>
	<div class="${ns}-footer">
		${Player.templates.footer(data)}
	</div>
	<input class="${ns}-add-local-file-input" type="file" style="display: none" accept="image/*,.webm" multiple>
</div>`

/***/ }),

/***/ "./src/templates/colorpicker.tpl":
/*!***************************************!*\
  !*** ./src/templates/colorpicker.tpl ***!
  \***************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(Icons) {module.exports = (data = {}) => `<div class="${ns}-colorpicker dialog" style="top: 0px; left: 0px;">
	<div class="${ns}-cp-saturation" style="height: ${data.HEIGHT}px; width: ${data.WIDTH}px;">
		<div class="position" style="left: ${data.WIDTH - 3}px; top: -3px;"></div>
	</div>
	<div class="${ns}-cp-hue" style="height: ${data.HEIGHT}px">
		<div class="position"></div>
	</div>
	<div class="${ns}-output" style="text-align: right;">
		<a href="#" class="${ns}-close-colorpicker">${Icons.close}</a>
		<div class="output-color" style="background: rgb(${data.rgb[0]}, ${data.rgb[1]}, ${data.rgb[2]});"></div>

		<table>
			<tr><td>R:</td><td><input type="text" class="${ns}-rgb-input" data-color="0" value="${data.rgb[0]}"></td></tr>
			<tr><td>G:</td><td><input type="text" class="${ns}-rgb-input" data-color="1" value="${data.rgb[1]}"></td></tr>
			<tr><td>B:</td><td><input type="text" class="${ns}-rgb-input" data-color="2" value="${data.rgb[2]}"></td></tr>
			<tr><td>A:</td><td><input type="text" class="${ns}-rgb-input" data-color="3" value="${data.rgb[3]}"></td></tr>
		</table>

		<button class="${ns}-apply-colorpicker">Apply</button><br>
	</div>
</div>`
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/icons */ "./src/icons.js")))

/***/ }),

/***/ "./src/templates/controls.tpl":
/*!************************************!*\
  !*** ./src/templates/controls.tpl ***!
  \************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(Icons) {module.exports = (data = {}) => `<div class="${ns}-col-auto">
	<div class="${ns}-media-control ${ns}-previous-button ${ns}-hover-fill" data-hide-id="previous">
		${Icons.skipStart} ${Icons.skipStartFill}
	</div>
	<div class="${ns}-media-control ${ns}-play-button ${ns}-hover-fill ${!Player.audio || Player.audio.paused ? `${ns}-play` : ''}">
		${Icons.play} ${Icons.pause} ${Icons.playFill} ${Icons.pauseFill}
	</div>
	<div class="${ns}-media-control ${ns}-next-button ${ns}-hover-fill" data-hide-id="next">
		${Icons.skipEnd} ${Icons.skipEndFill}
	</div>
</div>
<div class="${ns}-col" data-hide-id="seek-bar">
	<div class="${ns}-seek-bar ${ns}-progress-bar">
		<div class="${ns}-full-bar">
			<div class="${ns}-loaded-bar"></div>
			<div class="${ns}-current-bar"></div>
		</div>
	</div>
</div>
<div class="${ns}-col-auto" data-hide-id="time">
	<span>
		<span class="${ns}-current-time">0:00</span>
		<span class="${ns}-text-muted" data-hide-id="duration"> / <span class="${ns}-duration">0:00</span></span>
	</span>
</div>
<div class="${ns}-col-auto" data-hide-id="mute">
	<div class="${ns}-media-control ${ns}-volume-button ${ns}-hover-fill up" data-hide-id="volume-button">
		${Icons.volumeMute} ${Icons.volumeMuteFill}
		${Icons.volumeUp} ${Icons.volumeUpFill}
	</div>
	<div class="${ns}-volume-bar ${ns}-progress-bar" data-hide-id="volume-bar">
		<div class="${ns}-full-bar">
			<div class="${ns}-current-bar" style="width: ${Player.audio.volume * 100}%"></div>
		</div>
	</div>
</div>
<div class="${ns}-col-auto" data-hide-id="fullscreen">
	<div class="${ns}-media-control ${ns}-fullscreen-button">
		${Icons.fullscreen} ${Icons.fullscreenExit}
	</div>
</div>`
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/icons */ "./src/icons.js")))

/***/ }),

/***/ "./src/templates/footer.tpl":
/*!**********************************!*\
  !*** ./src/templates/footer.tpl ***!
  \**********************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = (data = {}) => Player.userTemplate.build({
	template: Player.config.footerTemplate,
	location: 'footer',
	sound: Player.playing
})
+ `<div class="${ns}-expander"></div>`

/***/ }),

/***/ "./src/templates/header.tpl":
/*!**********************************!*\
  !*** ./src/templates/header.tpl ***!
  \**********************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = (data = {}) => Player.userTemplate.build({
	template: Player.config.headerTemplate,
	location: 'header',
	sound: Player.playing,
	defaultName: '4chan Sounds',
	outerClass: `${ns}-col-auto`
});


/***/ }),

/***/ "./src/templates/host_input.tpl":
/*!**************************************!*\
  !*** ./src/templates/host_input.tpl ***!
  \**************************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = (data = {}) => {
	// data is the host name
	const host = Player.config.uploadHosts[data];
	if (!host) {
		return '';
	}
	return `<div class="${ns}-row ${ns}-col ${ns}-host-input ${host.invalid ? 'invalid' : ''}" data-host-name="${data}">
		<div class="${ns}-row ${ns}-host-controls">
			<div class="${ns}-col-auto">
				<label>
					<input type="checkbox" data-property="defaultUploadHost" ${Player.config.defaultUploadHost === data ? 'checked': ''}>
					Default
				</label>
			</div>
			<div class="${ns}-col-auto"><a href="#" class="${ns}-heading-action ${ns}-remove-host" data-handler="settings.removeHost" data-property="uploadHosts">Remove</a></div>
		</div>
		<div class="${ns}-row">
			<div class="${ns}-col"><input type="text" data-property="uploadHosts" name="name" value="${data || ''}" placeholder="Name"></div>
			<div class="${ns}-col"><input type="text" data-property="uploadHosts" name="url" value="${host.url || ''}" placeholder="URL"></div>
			<div class="${ns}-col"><input type="text" data-property="uploadHosts" name="responsePath" value="${host.responsePath || ''}" placeholder="Response Path"></div>
			<div class="${ns}-col"><input type="text" data-property="uploadHosts" name="responseMatch" value="${host.responseMatch || ''}" placeholder="Response Match"></div>
			<div class="${ns}-col"><input type="text" data-property="uploadHosts" name="soundUrl" value="${host.soundUrl || ''}" placeholder="File URL Format"></div>
		</div>
		<div class="${ns}-row">
			<div class="${ns}-col"><textarea data-property="uploadHosts" name="data" placeholder="Data (JSON)">${JSON.stringify(host.data, null, 4)}</textarea></div>
		</div>
		<div class="${ns}-row">
			<div class="${ns}-col"><textarea data-property="uploadHosts" name="headers" placeholder="Headers (JSON)">${host.headers ? JSON.stringify(host.headers, null, 4) : ''}</textarea></div>
		</div>
	</div>`;
}

/***/ }),

/***/ "./src/templates/item_menu.tpl":
/*!*************************************!*\
  !*** ./src/templates/item_menu.tpl ***!
  \*************************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = (data = {}) => `<div class="${ns}-menu dialog" id="menu" tabindex="0" data-type="post" style="position: fixed;">
	<a class="${ns}-remove-link entry focused" href="javascript:;" data-id="${data.sound.id}">Remove</a>
	${data.sound.post ? `<a class="entry" href="#${data.postIdPrefix + data.sound.post}">Show Post</a>` : ''}
	<div class="entry has-submenu">
		Open
		<div class="dialog submenu" style="inset: 0px auto auto 100%;">
			<a class="entry" href="${data.sound.image}" target="_blank">Image</a>
			<a class="entry" href="${data.sound.src}" target="_blank">Sound</a>
		</div>
	</div>
	<div class="entry has-submenu">
		Download
		<div class="dialog submenu" style="inset: 0px auto auto 100%;">
			<a class="${ns}-download-link entry" href="javascript:;" data-src="${data.sound.image}" data-name="${data.sound.filename}">Image</a>
			<a class="${ns}-download-link entry" href="javascript:;" data-src="${data.sound.src}" data-name="${data.sound.name}">Sound</a>
		</div>
	</div>
	<div class="entry has-submenu">
		Filter
		<div class="dialog submenu" style="inset: 0px auto auto 100%;">
			${data.sound.imageMD5 ? `<a class="${ns}-filter-link entry" href="javascript:;" data-filter="${data.sound.imageMD5}">Image</a>` : ''}
			<a class="${ns}-filter-link entry" href="javascript:;" data-filter="${data.sound.src.replace(/^(https?\:)?\/\//, '')}">Sound</a>
		</div>
	</div>
</div>`


/***/ }),

/***/ "./src/templates/list.tpl":
/*!********************************!*\
  !*** ./src/templates/list.tpl ***!
  \********************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = (data = {}) => (data.sounds || Player.sounds).map(sound =>
	`<div class="${ns}-list-item ${ns}-row ${sound.playing ? 'playing' : ''}" data-id="${sound.id}" ${!Player.playlist.matchesSearch(sound) ? 'style="display: none"' : ''} draggable="true">
		${Player.userTemplate.build({
			template: Player.config.rowTemplate,
			location: 'item-' + sound.id,
			sound,
			outerClass: `${ns}-col-auto`
		})}
	</div>`
).join('')

/***/ }),

/***/ "./src/templates/player.tpl":
/*!**********************************!*\
  !*** ./src/templates/player.tpl ***!
  \**********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(Icons) {module.exports = (data = {}) => `<div class="${ns}-media">
	<a class="${ns}-image-link" target="_blank">
		<img class="${ns}-image" src="data:image/svg+xml;base64,${btoa(Icons.fcSounds)}"></img>
		<video class="${ns}-video"></video>
	</a>
	<div class="${ns}-controls ${ns}-row">
		${Player.templates.controls(data)}
	</div>
</div>
<input class="${ns}-playlist-search" type="input" placeholder="Search" style="min-width: 100%; box-sizing: border-box; ${!Player.config.showPlaylistSearch ? 'display: none;' : ''}">
<div class="${ns}-list-container" style="height: 100px">
	${Player.templates.list(data)}
</div>
<img class="${ns}-hover-image">`
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/icons */ "./src/icons.js")))

/***/ }),

/***/ "./src/templates/settings.tpl":
/*!************************************!*\
  !*** ./src/templates/settings.tpl ***!
  \************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(_) {module.exports = (data = {}) => {
	const settingsConfig = __webpack_require__(/*! config */ "./src/config/index.js");
	const groups = settingsConfig.reduce((groups, setting) => {
		if (setting.displayGroup) {
			groups[setting.displayGroup] || (groups[setting.displayGroup] = []);
			groups[setting.displayGroup].push(setting);
		}
		return groups;
	}, {});

	let tpl = `<div class="${ns}-settings-tabs ${ns}-row">
		${Object.keys(groups).map(name => 
			`<a href="javascript:;" class="${ns}-col-auto ${ns}-settings-tab ${Player.settings.view !== name ? '' : 'active'}" data-group="${name}">${name}</a>`
		).join(' | ')}
		| <a href="${Player.settings.changelog}" class="${ns}-col-auto ${ns}-settings-tab" target="_blank">v${"3.2.1"}</a>
	</div>`;

	Object.keys(groups).forEach(name => {
		tpl += `<div class="${ns}-settings-group ${Player.settings.view !== name ? '' : 'active'}" data-group="${name}">`;

		groups[name].forEach(function addSetting(setting) {
			// Filter settings with a null display method;
			if (setting.displayMethod === null) {
				return;
			}
			const desc = setting.description;

			tpl += `
			<div class="${ns}-row ${ns}-align-${setting.isSubSetting ? 'start' : 'center'} ${setting.isSubSetting ? `${ns}-sub-settings` : ''}">
				<div class="${ns}-col ${!setting.isSubSetting ? `${ns}-heading` : `${ns}-space-between`} ${desc ? `${ns}-has-description` : ''}" ${desc ? `title="${desc.replace(/"/g, '&quot;')}"` : ''}>
					${setting.title}
					${!setting.actions || !setting.actions.length ? '' : `<div style="display: inline-block; margin-right: .25rem">
						${(setting.actions || []).map(action => `<a href="#" class="${ns}-heading-action" data-handler="${action.handler}" data-property="${setting.property}">${action.title}</a>`).join(' ')}
					</div>`}
				</div>`;
				if (setting.text) {
					tpl += setting.dismissTextId
						? `<div class="${ns}-col" style="min-width: 100%">`
								+ Player.display.ifNotDismissed(
									setting.dismissTextId,
									setting.dismissRestoreText,
									`<div data-dismiss-id="${setting.dismissTextId}">`
										+ setting.text
										+ `<a href="javascript:;" class="${ns}-dismiss-link" data-dismiss="${setting.dismissTextId}" style="display:block; margin-top:.25rem">Dismiss</a>`
									+ `</div>`
								)
							+ `</div>`
						: setting.text;
				};

				if (setting.settings) {
					setting.settings.forEach(subSetting => addSetting({
						...setting,
						actions: null,
						settings: null,
						description: null,
						...subSetting,
						isSubSetting: true
					}));
				} else {

					let value = _.get(Player.config, setting.property, setting.default),
						attrs = (setting.attrs || '') + (setting.class ? ` class="${setting.class}"` : '') + ` data-property="${setting.property}"`,
						displayMethod = setting.displayMethod,
						displayMethodFunction = typeof displayMethod === 'function' ? displayMethod : _.get(Player, displayMethod);

					if (setting.format) {
						value = Player.getHandler(setting.format)(value);
					}
					let type = typeof value;

					if (setting.split) {
						value = value.join(setting.split);
					} else if (type === 'object') {
						value = JSON.stringify(value, null, 4);
					}

					tpl += typeof displayMethodFunction === 'function'
							? displayMethodFunction(value, attrs)

						: type === 'boolean'
							? `<div class="${ns}-col"><input type="checkbox" ${attrs} ${value ? 'checked' : ''}></div>`

						: displayMethod === 'textarea' || type === 'object'
							? `<div class="${ns}-row ${ns}-col"><textarea ${attrs}>${value}</textarea></div>`

						: setting.options
							? `<div class="${ns}-col">
								<select ${attrs}>
									${Object.keys(setting.options).map(k => `<option value="${k}" ${value === k ? 'selected' : ''}>
										${setting.options[k]}
									</option>`).join('')}
								</select>
							</div>`

						: `<div class="${ns}-col"><input type="text" ${attrs} value="${value}"></div>`;
				}
			tpl += '</div>';
		});

		tpl += '</div>';
	});

	return tpl;
}
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/_ */ "./src/_.js")))

/***/ }),

/***/ "./src/templates/thread_boards.tpl":
/*!*****************************************!*\
  !*** ./src/templates/thread_boards.tpl ***!
  \*****************************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = (data = {}) => (Player.threads.boardList || []).map(board => {
	let checked = Player.threads.selectedBoards.includes(board.board);
	return !checked && !Player.threads.showAllBoards
		? ''
		: `<label>
			<input type="checkbox" value="${board.board}" ${checked ? 'checked' : ''}>
			/${board.board}/
		</label>`
}).join('')

/***/ }),

/***/ "./src/templates/thread_list.tpl":
/*!***************************************!*\
  !*** ./src/templates/thread_list.tpl ***!
  \***************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(_) {module.exports = (data = {}) => Object.keys(Player.threads.displayThreads).reduce((rows, board) => {
	return rows.concat(Player.threads.displayThreads[board].map(thread => `
		<tr>
			<td>
				<a class="quotelink" href="//boards.${thread.ws_board ? '4channel' : '4chan'}.org/${thread.board}/thread/${thread.no}#p${thread.no}" target="_blank">
					>>>/${thread.board}/${thread.no}
				</a>
			</td>
			<td>${thread.sub || ''}</td>
			<td>${thread.replies} / ${thread.images}</td>
			<td>${_.timeAgo(thread.time)}</td>
			<td>${_.timeAgo(thread.last_modified)}</td>
		</tr>
	`))
}, []).join('')

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/_ */ "./src/_.js")))

/***/ }),

/***/ "./src/templates/threads.tpl":
/*!***********************************!*\
  !*** ./src/templates/threads.tpl ***!
  \***********************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = (data = {}) => `<div class="${ns}-heading ${ns}-has-description" title="Search for threads with a sound OP">
	Active Threads
	${!Player.threads.loading ? `- <a class="${ns}-fetch-threads-link ${ns}-heading-action" href="#">Update</a>` : ''}
</div>
<div style="display: ${Player.threads.loading ? 'block' : 'none'}">Loading</div>
<div style="display: ${Player.threads.loading ? 'none' : 'block'}">
	<div class="${ns}-heading ${ns}-has-description" title="Only includes threads containing the search.">
		Filter
	</div>
	<input type="text" class="${ns}-threads-filter" value="${Player.threads.filterValue || ''}"></input>
	<div class="${ns}-heading">
		Boards - <a class="${ns}-all-boards-link ${ns}-heading-action" href="#">${Player.threads.showAllBoards ? 'Selected Only' : 'Show All'}</a>
	</div>
	<div class="${ns}-thread-board-list">
		${Player.templates.threadBoards(data)}
	</div>
	${!Player.threads.hasParser
		? ''
		: `<div class="${ns}-heading" style="text-align: center">
			${Player.config.threadsViewStyle !== 'table'
				? `<a class="${ns}-threads-view-style ${ns}-heading-action" style="margin: 0" data-style="table" href="#">Table</a>`
				: `<span>Table</span>`}
			|
			${Player.config.threadsViewStyle !== 'board'
				? `<a class="${ns}-threads-view-style ${ns}-heading-action" style="margin: 0" data-style="board" href="#">Board</a>`
				: `<span>Board</span>`}
		</div>`
	}
	${
		!Player.threads.hasParser || Player.config.threadsViewStyle === 'table'
		? `<table>
				<tr>
					<th>Thread</th>
					<th>Subject</th>
					<th>Replies/Images</th>
					<th>Started</th>
					<th>Updated</th>
				<tr>
				<tbody class="${ns}-threads-body"></tbody>
			</table>`
		: `<div class="${ns}-thread-list"></div>`
	}
</div>`


/***/ }),

/***/ "./src/templates/tools.tpl":
/*!*********************************!*\
  !*** ./src/templates/tools.tpl ***!
  \*********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(Icons) {module.exports = (data = {}) => `<div class="${ns}-heading">Encode / Decode URL</div>
<div class="${ns}-row">
	<div class="${ns}-col"><input type="text" class="${ns}-decoded-input" placeholder="https://"></div>
<div class="${ns}-col"><input type="text" class="${ns}-encoded-input" placeholder="https%3A%2F%2F"></div>
</div>

<div class="${ns}-heading">
	Create Sound Image
</div>
<div class="${ns}-create-sound-form">
	<div class="${ns}-row" style="margin-bottom: .5rem">
		${Player.display.ifNotDismissed('createSoundDetails', 'Show Help',
		`<div class="${ns}-col" data-dismiss-id="createSoundDetails">
			Select an image and sound to combine as a sound image.
			The sound will be uploaded to the selected file host and the url will be added to the image filename.
			If you have an account for a host that you would like to use then make the required changes in the <a class="${ns}-host-setting-link" href="#">host config</a>.
			That typically means providing a user token in the data or headers.<br>
			${Player.tools.hasFFmpeg
				? 'Selecting a webm with audio as the image will split it into a video only webm to be posted and ogg audio file to be uploaded.'
				: 'For a webm with audio first split the webm into a separate video and audio file and select them both.'
			}
			<br>
			Multiple sound files, or a comma-separated list of sound URLs, can be given for a single image.
			If you do have multiple sounds the name will also be a considered comma-separated list.<br>
			<a href="javascript:;" class="${ns}-dismiss-link" data-dismiss="createSoundDetails">Dismiss</a>
		</div>`)}
	</div>
	<div class="${ns}-row">
		Host
	</div>
	<div class="${ns}-row">
		<div class="${ns}-col">
			<select class="${ns}-create-sound-host">
				${Object.keys(Player.config.uploadHosts).map((hostId, i) =>
					Player.config.uploadHosts[hostId] && !Player.config.uploadHosts[hostId].invalid
						? `<option value="${hostId}" ${Player.config.defaultUploadHost === hostId ? 'selected' : ''}>${hostId}</option>`
						: ''
				).join('')}
			</select>
		</div>
	</div>
	<div class="${ns}-row" style="margin-top: .25rem">
		Data
	</div>
	<div class="${ns}-row">
		<div class="${ns}-col">
			<input type="text" class="${ns}-create-sound-name" placeholder="Name/s">
		</div>
	</div>
	<div class="${ns}-row">
		<div class="${ns}-col">
			<div class="${ns}-file-input placeholder">
				<div class="${ns}-file-overlay">
				<span class="placeholder-text">Select/Drop Image</span>
				<span class="text"></span>
				<input class="${ns}-create-sound-img" type="file" accept="image/*,.webm">
				</div>
			</div>
		</div>
		<div class="${ns}-col">
			<div class="${ns}-file-input placeholder" ${Player.tools.useSoundURL ? 'display: none;' : ''}>
				<div class="${ns}-file-overlay">
					<span class="placeholder-text">Select/Drop Sound/s</span>
					<span class="text"></span>
					<div class="overfile ${ns}-input-append">
						${Player.tools.hasFFmpeg && `<label class="${ns}-use-video-label" style="display: none;">Use video<input type="checkbox" class="${ns}-use-video"></label>` || ''}
						<a href="#" class="${ns}-toggle-sound-input" data-type="url" title="Enter a URL of a previously uploaded file.">${Icons.link}</a>
					</div>
					<input class="${ns}-create-sound-snd" type="file" accept="audio/*,video/*" multiple>
				</div>
				<div class="${ns}-file-list"></div>
			</div>
			<div class="${ns}-row ${ns}-align-center" style="position: relative; ${Player.tools.useSoundURL ? '' : 'display: none;'}">
				<a href="#" class="${ns}-toggle-sound-input ${ns}-input-append" data-type="file" title="Select a file to upload.">
					${Icons.fileEarmarkMusic}
				</a>
				<input type="text" class="${ns}-create-sound-snd-url" placeholder="Sound URL/s" style="min-width: 100%;">
			</div>
		</div>
	</div>
	<div class="${ns}-row" style="margin-top: .5rem">
		<div class="${ns}-col-auto">
			<button class="${ns}-create-button">Create</button>
		</div>
	</div>
</div>
<div class="${ns}-create-sound-status" ${Player.tools.createStatusText ? '' : 'style="display: none"'}>
	${Player.tools.createStatusText}
</div>`

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/icons */ "./src/icons.js")))

/***/ }),

/***/ "./src/templates/views_menu.tpl":
/*!**************************************!*\
  !*** ./src/templates/views_menu.tpl ***!
  \**************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(Icons) {module.exports = (data = {}) => `<div class="${ns}-menu dialog" id="menu" tabindex="0" data-type="post" style="position: fixed;">
	${[ 'playlist', 'image' ].includes(Player.config.viewStyle) ? ''
		: `<a class="${ns}-row nowrap ${ns}-align-center ${ns}-player-button entry" href="javascript:;"><div class="${ns}-col">Player</div><div class="${ns}-col-auto">${Icons.musicNoteList}</div></a>`}
	${Player.config.viewStyle === 'settings' ? ''
		: `<a class="${ns}-row nowrap ${ns}-align-center ${ns}-config-button entry" href="javascript:;"><div class="${ns}-col">Settings</div><div class="${ns}-col-auto">${Icons.gear}</div></span></a>`}
	${Player.config.viewStyle === 'threads' ? ''
		: `<a class="${ns}-row nowrap ${ns}-align-center ${ns}-threads-button entry" href="javascript:;"><div class="${ns}-col">Threads</div><div class="${ns}-col-auto">${Icons.search}</div></span></a>`}
	${Player.config.viewStyle === 'tools' ? ''
		: `<a class="${ns}-row nowrap ${ns}-align-center ${ns}-tools-button entry" href="javascript:;"><div class="${ns}-col">Tools</div><div class="${ns}-col-auto">${Icons.tools}</div></span></a>`}
</div>`

/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./src/icons */ "./src/icons.js")))

/***/ })

/******/ });