diff --git a/dist/ethicalads.js b/dist/ethicalads.js
index 7de52c9..16f4995 100644
--- a/dist/ethicalads.js
+++ b/dist/ethicalads.js
@@ -95,7 +95,7 @@ var ethicalads =
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Placement\", function() { return Placement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"check_dependencies\", function() { return check_dependencies; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"load_placements\", function() { return load_placements; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"unload_placements\", function() { return unload_placements; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"set_verbosity\", function() { return set_verbosity; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"wait\", function() { return wait; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"load\", function() { return load; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"reload\", function() { return reload; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"uplifted\", function() { return uplifted; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"detectedKeywords\", function() { return detectedKeywords; });\n/* harmony import */ var verge__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! verge */ \"./node_modules/verge/verge.js\");\n/* harmony import */ var verge__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(verge__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./styles.scss */ \"./styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_1__);\nfunction _typeof(o) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o; }, _typeof(o); }\nfunction _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }\nfunction _possibleConstructorReturn(t, e) { if (e && (\"object\" == _typeof(e) || \"function\" == typeof e)) return e; if (void 0 !== e) throw new TypeError(\"Derived constructors may only return object or undefined\"); return _assertThisInitialized(t); }\nfunction _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); return e; }\nfunction _inherits(t, e) { if (\"function\" != typeof e && null !== e) throw new TypeError(\"Super expression must either be null or a function\"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, \"prototype\", { writable: !1 }), e && _setPrototypeOf(t, e); }\nfunction _wrapNativeSuper(t) { var r = \"function\" == typeof Map ? new Map() : void 0; return _wrapNativeSuper = function _wrapNativeSuper(t) { if (null === t || !_isNativeFunction(t)) return t; if (\"function\" != typeof t) throw new TypeError(\"Super expression must either be null or a function\"); if (void 0 !== r) { if (r.has(t)) return r.get(t); r.set(t, Wrapper); } function Wrapper() { return _construct(t, arguments, _getPrototypeOf(this).constructor); } return Wrapper.prototype = Object.create(t.prototype, { constructor: { value: Wrapper, enumerable: !1, writable: !0, configurable: !0 } }), _setPrototypeOf(Wrapper, t); }, _wrapNativeSuper(t); }\nfunction _construct(t, e, r) { if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); var o = [null]; o.push.apply(o, e); var p = new (t.bind.apply(t, o))(); return r && _setPrototypeOf(p, r.prototype), p; }\nfunction _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }\nfunction _isNativeFunction(t) { try { return -1 !== Function.toString.call(t).indexOf(\"[native code]\"); } catch (n) { return \"function\" == typeof t; } }\nfunction _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }\nfunction _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }\nfunction _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\"); }\nfunction _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }\nfunction _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", { writable: !1 }), e; }\nfunction _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == _typeof(i) ? i : i + \"\"; }\nfunction _toPrimitive(t, r) { if (\"object\" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != _typeof(i)) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n/* Ethical ad publisher JavaScript client\n *\n * Loads placement from Ethical Ad decision API. Searches for elements with\n * `ethical-ad` data binding attributes and uses these attributes to query the\n * decision API.\n *\n * This is native JavaScript, no JQuery. It uses the API JSONP interface to get\n * around CORS and related issues. A script is added with a callback on\n * `window`. The promise is rejected if there are errors with the request or the\n * response doesn't look correct.\n *\n * Currently, only two parameters are supported with the ad placement: publisher\n * id and the place type. All of this is determined by the server and this\n * client so far only renders the API return HTML.\n *\n * This can be loaded async. CSS styles are preloaded via webpack `style-loader`.\n * There is some potential for problems if CSP rules disallow inline\n * stylesheets, but webpack does allow for a hardcoded nonce.\n *\n * Usage:\n *\n * \n *
\n */\n\n\n\nvar AD_CLIENT_VERSION = \"2.3.0\"; // Sent with the ad request\n\n// For local testing, set this\n// const AD_DECISION_URL = \"http://ethicaladserver:5000/api/v1/decision/\";\nvar AD_DECISION_URL = \"https://server.ethicalads.io/api/v1/decision/\";\nvar AD_TYPES_VERSION = 1; // Used with the ad type slugs\nvar ATTR_PREFIX = \"data-ea-\";\nvar ABP_DETECTION_PX = \"https://media.ethicalads.io/abp/px.gif\";\n\n// Verbosity and logging\n//\n// Set with:\n//\n// \nvar VERBOSITY = {\n quiet: 0,\n // Errors only\n normal: 1,\n // Warnings only (default)\n verbose: 2 // Debug messages\n};\nvar logger = {\n verbosity: VERBOSITY[\"normal\"],\n // Default\n debug: function debug(message) {\n if (this.verbosity >= VERBOSITY[\"verbose\"]) {\n var _console;\n for (var _len = arguments.length, params = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n params[_key - 1] = arguments[_key];\n }\n (_console = console).debug.apply(_console, [message].concat(params));\n }\n },\n info: function info(message) {\n if (this.verbosity >= VERBOSITY[\"verbose\"]) {\n var _console2;\n for (var _len2 = arguments.length, params = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n params[_key2 - 1] = arguments[_key2];\n }\n (_console2 = console).info.apply(_console2, [message].concat(params));\n }\n },\n warn: function warn(message) {\n if (this.verbosity >= VERBOSITY[\"normal\"]) {\n var _console3;\n for (var _len3 = arguments.length, params = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {\n params[_key3 - 1] = arguments[_key3];\n }\n (_console3 = console).warn.apply(_console3, [message].concat(params));\n }\n },\n error: function error(message) {\n if (this.verbosity >= VERBOSITY[\"quiet\"]) {\n var _console4;\n for (var _len4 = arguments.length, params = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {\n params[_key4 - 1] = arguments[_key4];\n }\n (_console4 = console).error.apply(_console4, [message].concat(params));\n }\n }\n};\n\n// Keywords and topics\n//\n// This allows us to categorize pages simply and have better content targeting.\n// Additional categorization can be done on the server side for pages\n// that request ads commonly but this quick and easy categorization\n// works decently well most of the time.\nvar KEYWORDS = new Set([\"2fa\", \"ai\", \"airflow\", \"algolia\", \"android\", \"angular\", \"angularjs\", \"ansible\", \"api\", \"appengine\", \"app-engine\", \"arangodb\", \"artificial-intelligence\", \"asp-net\", \"auth0\", \"authentication\", \"authorization\", \"aws\", \"azure\", \"babel\", \"backend\", \"backend-web\", \"bayes\", \"bayesian\", \"billing\", \"bitcoin\", \"blender\", \"blockchain\", \"celery\", \"chartjs\", \"chatbot\", \"chatbots\", \"chatgpt\", \"chatgpt3\", \"chatgpt4\", \"ci\", \"cicd\", \"ci-cd\", \"classifier\", \"cloud\", \"cloudformation\", \"cloud-formation\", \"cloudfront\", \"clustering\", \"cockroachdb\", \"commonjs\", \"computer-vision\", \"container\", \"containers\", \"continuousdeployment\", \"continuous-deployment\", \"continuousintegration\", \"continuous-integration\", \"cordova\", \"cplusplus\", \"cryptocurrency\", \"cryptography\", \"csharp\", \"c-sharp\", \"css\", \"cssinjs\", \"cuda\", \"cve\", \"cyber-attack\", \"cybersecurity\", \"cyber-security\", \"d3js\", \"dalle\", \"dall-e\", \"dao\", \"dapp\", \"dataanalytics\", \"data-analytics\", \"database\", \"datadog\", \"datalake\", \"data-lake\", \"datamesh\", \"data-mesh\", \"datascience\", \"data-science\", \"datascientist\", \"data-scientist\", \"data-visualization\", \"data-warehouse\", \"decryption\", \"deeplearning\", \"deep-learning\", \"deepreinforcement\", \"deep-reinforcement\", \"defi\", \"devops\", \"django\", \"djangorestframework\", \"django-rest-framework\", \"dnssec\", \"docker\", \"dockerhub\", \"docker-hub\", \"dockerizing\", \"dogecoin\", \"dotnet\", \"duckdb\", \"elasticsearch\", \"elastic-search\", \"emberjs\", \"erlang\", \"es6\", \"eslint\", \"ethereum\", \"express\", \"facedetection\", \"face-detection\", \"fiddler\", \"firebase\", \"firewall\", \"flask\", \"frontend\", \"frontend-web\", \"fsharp\", \"full-stack\", \"game\", \"gamedev\", \"gatsbyjs\", \"gcp\", \"gitguardian\", \"godot\", \"golang\", \"google-cloud\", \"gpt\", \"grafana\", \"grails\", \"graphql\", \"hacking\", \"haskell\", \"heroku\", \"hyperledger\", \"indiegame\", \"indie-game\", \"influxdb\", \"infosec\", \"invoice\", \"ionic\", \"ios\", \"ipfs\", \"iphone\", \"java\", \"javascript\", \"jenkins\", \"jfrog\", \"jinja\", \"jquery\", \"julia\", \"jupyter\", \"jvm\", \"kafka\", \"k-means-clustering\", \"kotlin\", \"kubernetes\", \"laravel\", \"lint\", \"linux\", \"llm\", \"llms\", \"log4j\", \"lucene\", \"machinelearning\", \"machine-learning\", \"mariadb\", \"matlab\", \"matplotlib\", \"maven\", \"metabase\", \"mfa\", \"midjourney\", \"minecraft\", \"mkdocs\", \"ml\", \"mobile\", \"model-training\", \"mongodb\", \"monitoring\", \"montecarlo\", \"monte-carlo\", \"mysql\", \"naivebayes\", \"naive-bayes\", \"neo4j\", \"neuralnet\", \"neural-net\", \"neural-nets\", \"neuralnetworks\", \"neural-networks\", \"newrelic\", \"new-relic\", \"nft\", \"nginx\", \"nlp\", \"node\", \"nodejs\", \"nosql\", \"numpy\", \"nuxt\", \"nuxtjs\", \"oauth\", \"obj-c\", \"objectdetection\", \"object-detection\", \"openai\", \"opencv-python-library\", \"openid\", \"openid-connect\", \"openjdk\", \"openshift\", \"openssl\", \"otp\", \"overfitting\", \"owasp\", \"pandas\", \"payment\", \"payments\", \"paypal\", \"penetration-test\", \"pentest\", \"perl\", \"phishing\", \"phonegap\", \"php\", \"pip\", \"postcss\", \"postgres\", \"postgresql\", \"privacy\", \"psf\", \"pwa\", \"pydata\", \"pygame\", \"pylint\", \"pypi\", \"pytest\", \"python\", \"pytorch\", \"pytorch3d\", \"rabbitmq\", \"rails\", \"rdbms\", \"rds\", \"react\", \"reactjs\", \"react-native\", \"redis\", \"redux\", \"regression\", \"regressionmodel\", \"regression-model\", \"reinforcement-learning\", \"rollbar\", \"ruby\", \"rust\", \"saltstack\", \"scala\", \"scikitlearn\", \"scikit-learn\", \"scipy\", \"scss\", \"security\", \"securityvulnerabilities\", \"security-vulnerabilities\", \"selenium\", \"selinux\", \"sencha\", \"sentiment-analysis\", \"sentry\", \"serverless\", \"single-page-application\", \"sklearn\", \"smartphone\", \"sms\", \"snowflake\", \"snyk\", \"solana\", \"solidity\", \"solr\", \"spa\", \"spacy\", \"sphinx\", \"sphinx-doc\", \"spring\", \"sql\", \"sqlite\", \"sqlserver\", \"sql-server\", \"stripe\", \"struts\", \"subscriptions\", \"svelte\", \"sveltejs\", \"swift\", \"symfony\", \"tableau\", \"tailwind\", \"tailwindcss\", \"tailwind-css\", \"tdd\", \"technical-writing\", \"tensor\", \"tensorflow\", \"tensorflowjs\", \"terraform\", \"test-driven-development\", \"testing\", \"tests\", \"textacy\", \"timescale\", \"timeseries\", \"training-data\", \"transformers\", \"travisci\", \"twilio\", \"two-factor-auth\", \"two-factor-authentication\", \"typescript\", \"ubuntu\", \"unittest\", \"unity\", \"vision-api\", \"visualization\", \"vue\", \"vuejs\", \"vuetify\", \"vuex\", \"vulnerability\", \"waf\", \"web3\", \"webapp-firewall\", \"webapplicationfirewall\", \"web-application-firewall\", \"webcomponents\", \"web-components\", \"webpack\", \"websecurity\", \"web-security\", \"werkzeug\", \"wireshark\", \"wsgi\", \"yarn\", \"zapier\"]);\n\n// Maximum number of words of a document to analyze looking for keywords\n// This is simply a check against taking too much time on very long documents\nvar MAX_WORDS_ANALYZED = 9999;\n\n// Max number of detected keywords to send\n// Lowering this number means that only major topics of the page get sent on long pages\nvar MAX_KEYWORDS = 3;\n\n// Minimum number of occurrences of a keyword to consider it\nvar MIN_KEYWORD_OCCURRENCES = 2;\n\n// Time between checking whether the ad is in the viewport to count the time viewed\n// Time viewed is an important advertiser metric\nvar VIEW_TIME_INTERVAL = 1; // seconds\nvar VIEW_TIME_MAX = 5 * 60; // seconds\n\n// In-viewport fudge factor\n// A fudge factor of ~3 is needed for the case where the ad\n// is hidden off the side of the screen by a sliding sidebar\n// For example, if the right side of the ad is at x=0\n// or the left side of the ad is at the right side of the viewport\nvar VIEWPORT_FUDGE_FACTOR = -3; // px\n\n// An ad may be rotated if it has been visible for sufficient time\n// And there is user interaction such as a hashchange or visibilitychange.\n// We rotate no more than the maximum number of rotations.\n// Loading the ad the first time counts as the first rotation.\nvar MIN_VIEW_TIME_ROTATION_DURATION = 45; // seconds\nvar MAX_ROTATIONS = 3;\n\n// Enable ad rotation on hash change (intra-site nav)\nvar HASHCHANGE_ROTATION_ENABLE = true;\n\n// Seconds after a tab comes back into focus to rotate an ad.\nvar VISIBILITYCHANGE_ROTATION_ENABLE = true;\nvar VISIBILITYCHANGE_ROTATION_DELAY = 3; // seconds\n\n/* Placement object to query decision API and return an Element node\n *\n * @param {string} publisher - Publisher ID\n * @param {string} ad_type - Placement ad type id\n * @param {Element} target - Target element\n * @param {Object} options - Various options for configuring the placement such as:\n keywords, styles, campaign_types, load_manually, force_ad, force_campaign\n */\nvar Placement = /*#__PURE__*/function () {\n function Placement(publisher, ad_type, target, options) {\n _classCallCheck(this, Placement);\n this.publisher = publisher;\n this.ad_type = ad_type;\n this.target = target;\n\n // Options\n this.options = options;\n this.style = options.style;\n this.keywords = options.keywords || [];\n this.load_manually = options.load_manually;\n this.force_ad = options.force_ad;\n this.force_campaign = options.force_campaign;\n this.campaign_types = options.campaign_types || [];\n if (!this.campaign_types.length) {\n this.campaign_types = [\"paid\", \"publisher-house\", \"community\", \"house\"];\n }\n\n // Initialized and will be used in the future\n this.view_time = 0;\n this.view_time_sent = false; // true once the view time is sent to the server\n this.response = null;\n this.tab_hidden = false;\n this.rotations = 1;\n this.index = null;\n }\n\n /* Create a placement from an element\n *\n * Returns null if the placement is already loaded.\n *\n * @static\n * @param {Element} element - Load placement and append to this Element\n * @returns {Placement}\n */\n return _createClass(Placement, [{\n key: \"load\",\n value:\n /* Transforms target element into a placement\n *\n * This method organizes all of the operations to transform the placement\n * configuration wrapper `div` into an ad placement -- including starting the\n * API transaction, displaying the ad element,\n * and handling the viewport detection.\n *\n * @returns {Promise}\n */\n function load() {\n var _this = this;\n // Detect the keywords\n this.keywords = this.keywords.concat(this.detectKeywords());\n return this.fetch().then(function (element) {\n if (element === undefined) {\n throw new EthicalAdsWarning(\"Ad decision request blocked or invalid.\");\n }\n if (!element) {\n throw new EthicalAdsWarning(\"No ads to show.\");\n }\n\n // Add `loaded` class, signifying that the CSS styles should finally be\n // applied to the target element.\n var classes = _this.target.className || \"\";\n classes += \" loaded\";\n _this.target.className = classes.trim();\n\n // Make this element the only child element of the target element\n while (_this.target.firstChild) {\n _this.target.removeChild(_this.target.firstChild);\n }\n\n // Apply any styles based on the specified styling\n _this.applyStyles(element);\n _this.target.appendChild(element);\n return _this;\n }).then(function (placement) {\n // Detect when the ad is in the viewport\n // Add the view pixel to the DOM to count the view\n // Also count the time the ad is in view\n // this will be sent before the page/tab is closed or navigated away\n\n var viewport_detection = setInterval(function (element) {\n if (placement.inViewport(element)) {\n // This ad was seen!\n var pixel = document.createElement(\"img\");\n pixel.src = placement.response.view_url;\n if (uplifted) {\n pixel.src += \"?uplift=true\";\n }\n pixel.className = \"ea-pixel\";\n element.appendChild(pixel);\n clearInterval(viewport_detection);\n }\n }, 100, placement.target);\n placement.view_time_counter = setInterval(function (element) {\n if (placement.tab_hidden === false && placement.inViewport(element)) {\n // Increment the ad's time in view counter\n placement.view_time += VIEW_TIME_INTERVAL;\n if (placement.view_time >= VIEW_TIME_MAX) {\n clearInterval(placement.view_time_counter);\n }\n }\n }, VIEW_TIME_INTERVAL * 1000, placement.target);\n placement.hashchange_listener = function () {\n if (placement.canRotate()) {\n placement.sendViewTime();\n placement.rotate();\n }\n };\n if (HASHCHANGE_ROTATION_ENABLE) {\n window.addEventListener(\"hashchange\", placement.hashchange_listener);\n }\n\n // Listens to the window visibility\n // Rotates the ad when the window comes back into focus if\n // other conditions (minimum view time, under max rotations)\n // are met.\n // When the tab loses focus, send the view time to the server.\n placement.visibilitychange_listener = function () {\n if (document.visibilityState === \"hidden\" || document.visibilityState === \"unloaded\") {\n // Check if the tab loses focus/is closed or the browser/app is minimized/closed\n // In that case, no longer count further time that the ad is in view\n // Send the time the ad was viewed to the server\n placement.tab_hidden = true;\n placement.sendViewTime();\n }\n\n // This tab was invisible and has come back into focus\n // Trigger an ad rotation\n if (placement.tab_hidden === true && document.visibilityState === \"visible\") {\n placement.tab_hidden = false;\n if (placement.canRotate()) {\n placement.sendViewTime(); // Should already be sent, but just in case\n setTimeout(function () {\n placement.rotate();\n }, VISIBILITYCHANGE_ROTATION_DELAY * 1000);\n }\n }\n };\n if (VISIBILITYCHANGE_ROTATION_ENABLE) {\n document.addEventListener(\"visibilitychange\", placement.visibilitychange_listener);\n }\n return _this;\n });\n }\n\n /* Clears all the placement's event listeners */\n }, {\n key: \"clearListeners\",\n value: function clearListeners() {\n if (this.view_time_counter) {\n clearInterval(this.view_time_counter);\n }\n if (this.hashchange_listener && HASHCHANGE_ROTATION_ENABLE) {\n window.removeEventListener(\"hashchange\", this.hashchange_listener);\n }\n if (this.visibilitychange_listener && VISIBILITYCHANGE_ROTATION_ENABLE) {\n document.removeEventListener(\"visibilitychange\", this.visibilitychange_listener);\n }\n }\n\n /* Returns whether the conditions to rotate are met\n *\n * @returns {boolean} True if the placement can rotate\n */\n }, {\n key: \"canRotate\",\n value: function canRotate() {\n if (!this.inViewport(this.target) || this.view_time < MIN_VIEW_TIME_ROTATION_DURATION || this.rotations >= MAX_ROTATIONS) {\n return false;\n }\n return true;\n }\n\n /* Reloads the placement with a new ad (if applicable)\n *\n * @returns {Promise}\n */\n }, {\n key: \"rotate\",\n value: function rotate() {\n if (!this.canRotate()) {\n return;\n }\n this.clearListeners();\n this.view_time = 0;\n this.view_time_sent = false;\n this.response = null;\n this.tab_hidden = false;\n this.rotations += 1;\n return this.load();\n }\n\n /* Returns whether the ad is visible in the viewport\n *\n * @param {Element} element - The ad element\n * @returns {boolean} True if the ad is loaded and visible in the viewport\n * (including the tab being focused and not minimized) and returns false otherwise.\n */\n }, {\n key: \"inViewport\",\n value: function inViewport(element) {\n if (this.response && this.response.view_url && verge__WEBPACK_IMPORTED_MODULE_0___default.a.inViewport(element, VIEWPORT_FUDGE_FACTOR) && document.visibilityState === \"visible\") {\n return true;\n }\n return false;\n }\n\n /* Get placement data from decision API\n *\n * @returns {Promise} Resolves with an Element converted from an HTML\n * string from API response. Can also be null, indicating a noop action.\n */\n }, {\n key: \"fetch\",\n value: function fetch() {\n var _this2 = this;\n // Make sure callbacks don't collide even with multiple placements\n var callback = \"ad_\" + Date.now() + \"_\" + Math.floor(Math.random() * 1000000);\n var div_id = callback;\n if (this.target.id) {\n div_id = this.target.id;\n }\n\n // There's no hard maximum on URL lengths (all of these get added to the query params)\n // but ideally we want to keep our URLs below ~2k which should work basically everywhere\n var params = {\n publisher: this.publisher,\n ad_types: this.ad_type,\n div_ids: div_id,\n callback: callback,\n keywords: this.keywords.join(\"|\"),\n campaign_types: this.campaign_types.join(\"|\"),\n format: \"jsonp\",\n client_version: AD_CLIENT_VERSION,\n placement_index: this.index,\n // location.href includes query params (possibly sensitive) and fragments (unnecessary)\n url: (window.location.origin + window.location.pathname).slice(0, 256)\n };\n if (this.force_ad) {\n params[\"force_ad\"] = this.force_ad;\n }\n if (this.force_campaign) {\n params[\"force_campaign\"] = this.force_campaign;\n }\n if (this.rotations > 1) {\n params[\"rotations\"] = this.rotations;\n }\n var url_params = new URLSearchParams(params);\n var url = new URL(AD_DECISION_URL + \"?\" + url_params.toString());\n return new Promise(function (resolve, reject) {\n window[callback] = function (response) {\n if (response && response.html && response.view_url) {\n _this2.response = response;\n var node_convert = document.createElement(\"div\");\n node_convert.innerHTML = response.html;\n return resolve(node_convert.firstChild);\n } else {\n // No ad to show for this targeting/publisher\n return resolve(null);\n }\n };\n var script = document.createElement(\"script\");\n script.src = url;\n script.type = \"text/javascript\";\n script.async = true;\n script.addEventListener(\"error\", function (err) {\n // There was a problem loading this request, likely this was blocked by\n // an ad blocker. We'll resolve with an empty response instead of\n // throwing an error.\n return resolve();\n });\n document.getElementsByTagName(\"head\")[0].appendChild(script);\n });\n }\n\n /* Sends the view time of the ad to the server\n */\n }, {\n key: \"sendViewTime\",\n value: function sendViewTime() {\n if (this.view_time <= 0 || this.view_time_sent || !this.response || !this.response.view_time_url) return;\n var pixel = document.createElement(\"img\");\n pixel.src = this.response.view_time_url + \"?view_time=\" + this.view_time;\n pixel.className = \"ea-pixel\";\n this.target.appendChild(pixel);\n this.view_time_sent = true;\n }\n\n /* Detect whether this ad is \"uplifted\" meaning allowed by ABP's Acceptable Ads list\n *\n * Calls the provided callback passing a boolean whether this ad is uplifted.\n * We need this data to provide back to the AcceptableAds folks.\n *\n * This code comes directly from Eyeo/AdblockPlus team to measure Acceptable Ads.\n *\n * @static\n * @param {string} px - A URL of a pixel to test\n * @param {function) callback - A callback to call when finished\n */\n }, {\n key: \"detectABP\",\n value: function detectABP(px, callback) {\n var detected = false;\n var checksRemain = 2;\n var error1 = false;\n var error2 = false;\n if (typeof callback != \"function\") return;\n px += \"?ch=*&rn=*\";\n function beforeCheck(callback, timeout) {\n if (checksRemain == 0 || timeout > 1e3) callback(checksRemain == 0 && detected);else setTimeout(function () {\n beforeCheck(callback, timeout * 2);\n }, timeout * 2);\n }\n function checkImages() {\n if (--checksRemain) return;\n detected = !error1 && error2;\n }\n var random = Math.random() * 11;\n var img1 = new Image();\n img1.onload = checkImages;\n img1.onerror = function () {\n error1 = true;\n checkImages();\n };\n img1.src = px.replace(/\\*/, 1).replace(/\\*/, random);\n var img2 = new Image();\n img2.onload = checkImages;\n img2.onerror = function () {\n error2 = true;\n checkImages();\n };\n img2.src = px.replace(/\\*/, 2).replace(/\\*/, random);\n beforeCheck(callback, 250);\n }\n\n /* Returns an array of keywords (strings) found on the page\n *\n * @returns {Array[string]} Advertising keywords found on the page\n */\n }, {\n key: \"detectKeywords\",\n value: function detectKeywords() {\n // Return previously detected keywords\n // If this code has already run.\n // Note: if there are \"no\" keywords (an empty list) this is still true\n if (detectedKeywords) return detectedKeywords;\n var keywordHist = {}; // Keywords found => count of keyword\n var mainContent = document.querySelector(\"[role='main']\") || document.querySelector(\"main\") || document.querySelector(\"body\");\n var words = mainContent.textContent.split(/\\s+/);\n var wordTrimmer = /^[\\('\"]?(.*?)[,\\.\\?\\!:;\\)'\"]?$/g;\n for (var x = 0; x < words.length && x < MAX_WORDS_ANALYZED; x++) {\n // Remove certain punctuation from beginning and end of the word\n var word = words[x].replace(wordTrimmer, \"$1\").toLowerCase();\n if (KEYWORDS.has(word)) {\n keywordHist[word] = (keywordHist[word] || 0) + 1;\n }\n }\n\n // Sort the hist with the most common items first\n // Grab only the MAX_KEYWORDS most common\n var keywords = Object.entries(keywordHist).filter(\n // Only consider a keyword with at least this many occurrences\n function (a) {\n return a[1] >= MIN_KEYWORD_OCCURRENCES;\n }).sort(function (a, b) {\n if (a[1] > b[1]) return -1;\n if (a[1] < b[1]) return 1;\n return 0;\n }).slice(0, MAX_KEYWORDS).map(function (x) {\n return x[0];\n });\n detectedKeywords = keywords;\n return keywords;\n }\n\n /* Apply custom styles based on data-ea-style\n *\n */\n }, {\n key: \"applyStyles\",\n value: function applyStyles(element) {\n // Stickybox: https://ethical-ad-client.readthedocs.io/en/latest/#stickybox\n if (this.style === \"stickybox\") {\n var hideButton = document.createElement(\"div\");\n hideButton.setAttribute(\"class\", \"ea-stickybox-hide\");\n hideButton.innerHTML = \"×\";\n hideButton.addEventListener(\"click\", function () {\n document.querySelector(\"[data-ea-publisher]\").remove();\n });\n element.appendChild(hideButton);\n }\n\n // FixedFooter: https://ethical-ad-client.readthedocs.io/en/latest/#fixedfooter\n if (this.style === \"fixedfooter\") {\n //element.querySelector('.ea-callout a').remove();\n\n var container = document.createElement(\"div\");\n container.setAttribute(\"class\", \"ea-fixedfooter-hide\");\n element.appendChild(container);\n var _hideButton = document.createElement(\"span\");\n _hideButton.append(\"Close Ad\");\n _hideButton.addEventListener(\"click\", function () {\n document.querySelector(\"[data-ea-publisher]\").remove();\n });\n container.appendChild(_hideButton);\n }\n\n // FixedHeader: https://ethical-ad-client.readthedocs.io/en/latest/#fixedheader\n // No special elements required\n }\n }], [{\n key: \"from_element\",\n value: function from_element(element) {\n // Get attributes from DOM node\n var publisher = element.getAttribute(ATTR_PREFIX + \"publisher\");\n var ad_type = element.getAttribute(ATTR_PREFIX + \"type\");\n if (!ad_type) {\n ad_type = \"image\";\n element.setAttribute(ATTR_PREFIX + \"type\", \"image\");\n }\n var keywords = (element.getAttribute(ATTR_PREFIX + \"keywords\") || \"\").split(\"|\").filter(function (word) {\n return word.length > 1;\n });\n var campaign_types = (element.getAttribute(ATTR_PREFIX + \"campaign-types\") || \"\").split(\"|\").filter(function (word) {\n return word.length > 1;\n });\n var load_manually = element.getAttribute(ATTR_PREFIX + \"manual\") === \"true\";\n var style = element.getAttribute(ATTR_PREFIX + \"style\");\n var force_ad = element.getAttribute(ATTR_PREFIX + \"force-ad\");\n var force_campaign = element.getAttribute(ATTR_PREFIX + \"force-campaign\");\n\n // Add version to ad type to verison the HTML return\n if (ad_type === \"image\" || ad_type === \"text\") {\n ad_type += \"-v\" + AD_TYPES_VERSION;\n }\n var classes = (element.className || \"\").split(\" \");\n if (classes.indexOf(\"loaded\") >= 0) {\n logger.warn(\"EthicalAd already loaded.\");\n return null;\n }\n\n // Note: this attribute value *must* contain a unit (eg. '200px')\n var placementBottom = element.getAttribute(ATTR_PREFIX + \"placement-bottom\");\n if (placementBottom) {\n element.style.setProperty(\"bottom\", placementBottom);\n }\n return new Placement(publisher, ad_type, element, {\n keywords: keywords,\n style: style,\n campaign_types: campaign_types,\n load_manually: load_manually,\n force_ad: force_ad,\n force_campaign: force_campaign\n });\n }\n }]);\n}();\n\n/* Detects whether the browser supports the necessary JS APIs to support the ad client\n *\n * Generally we support recent versions of evergreen browsers (Chrome, Firefox, Safari, Edge)\n * but we no longer support IE11.\n *\n * @returns {boolean} true if all dependencies met and false otherwise\n */\nfunction check_dependencies() {\n if (!Object.entries || !window.URL || !window.URLSearchParams || !window.Promise) {\n logger.error(\"Browser does not meet ethical ad client dependencies. Not showing ads\");\n return false;\n }\n return true;\n}\n\n/* Find all placement DOM elements and hot load HTML as child nodes\n *\n * @param {boolean} force_load - load placements even if they are set to load manually\n * @returns {Promise<[Placement]>} Resolves to a list of Placement instances\n */\nfunction load_placements() {\n var force_load = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n // Find all elements matching required data binding attribute.\n var node_list = document.querySelectorAll(\"[\" + ATTR_PREFIX + \"publisher]\");\n var elements = Array.prototype.slice.call(node_list);\n if (elements.length === 0) {\n logger.warn(\"No ad placements found.\");\n }\n\n // Create main promise. Iterator `all()` Promise will surround array of found\n // elements. If any of these elements have issues, this main promise will\n // reject.\n return Promise.all(elements.map(function (element, index) {\n var placement = Placement.from_element(element);\n if (!placement) {\n // Placement has already been loaded\n return null;\n }\n placement.index = index;\n\n // Run AcceptableAds detection code\n // This lets us know how many impressions are attributed to AceeptableAds\n // Only run this once even for multiple placements\n // All impressions will be correctly attributed\n if (index === 0 && placement && !force_load) {\n placement.detectABP(ABP_DETECTION_PX, function (usesABP) {\n uplifted = usesABP;\n if (usesABP) {\n logger.debug(\"Acceptable Ads enabled. Thanks for allowing our non-tracking ads :)\");\n }\n });\n }\n if (placement && (force_load || !placement.load_manually)) {\n return placement.load();\n } else {\n // This will be manually loaded later or has already been loaded\n return null;\n }\n }));\n}\nfunction unload_placements() {\n var node_list = document.querySelectorAll(\"[\" + ATTR_PREFIX + \"publisher]\");\n var elements = Array.prototype.slice.call(node_list);\n elements.forEach(function (div) {\n div.innerHTML = \"\";\n div.classList.remove(\"loaded\");\n });\n}\nfunction set_verbosity() {\n var element = document.querySelector(\"[\" + ATTR_PREFIX + \"publisher]\");\n if (element) {\n var user_verbosity = element.getAttribute(ATTR_PREFIX + \"verbosity\");\n if (VERBOSITY.hasOwnProperty(user_verbosity)) {\n logger.verbosity = VERBOSITY[user_verbosity];\n }\n }\n}\n\n// An error class that we will not surface to clients normally.\nvar EthicalAdsWarning = /*#__PURE__*/function (_Error) {\n function EthicalAdsWarning() {\n _classCallCheck(this, EthicalAdsWarning);\n return _callSuper(this, EthicalAdsWarning, arguments);\n }\n _inherits(EthicalAdsWarning, _Error);\n return _createClass(EthicalAdsWarning);\n}(/*#__PURE__*/_wrapNativeSuper(Error));\n/* Wrapping Promise to allow for handling of errors by user\n *\n * This promise currently does not reject on error as this will emit a console\n * warning if the user hasn't added a promise rejection handler (which is most\n * cases).\n *\n * This promise resolves to an aray of Placement instances, or an empty list if\n * there was any error configuring the placements.\n *\n * For example, to perform an action when no placements are loaded:\n *\n * \n *\n * @type {Promise<[Placement]>}\n */\nvar wait;\n\n/* Loading placements manually rather than the normal way\n *\n * \n * \n *\n * @type function\n */\nvar load;\n\n/* Reloading placements. Used by SPAs.\n * @type function\n */\nvar reload;\n\n/* Whether this ad impression is attributed to being on the Acceptable Ads list.\n * @type boolean\n */\nvar uplifted = false;\n\n/* Keywords detected on the page\n * @type {Array[string]}\n */\nvar detectedKeywords = null;\n\n/* If importing this as a module, do not automatically process DOM and fetch the\n * ad placement. Only do this if using the module directly, from a `script`\n * element. This will allow for future extension and packaging as a module.\n *\n * This also replicates JQuery `$(document).ready()`, with added protection for\n * usage of `async` -- the DOM ready event can fire before the script is loaded..\n */\nif (window.ethicalads) {\n // Always display this warning regardless of log level\n // This is a code mistake by publishers and should be caught right away.\n console.warn(\"Double-loading the EthicalAds client. Use reload() instead. https://ethical-ad-client.readthedocs.io/en/latest/#single-page-apps\");\n}\nif (check_dependencies()) {\n // Set the client verbosity\n set_verbosity();\n var wait_dom = new Promise(function (resolve) {\n if (document.readyState === \"interactive\" || document.readyState === \"complete\") {\n return resolve();\n } else {\n document.addEventListener(\"DOMContentLoaded\", function () {\n resolve();\n }, {\n capture: true,\n once: true,\n passive: true\n });\n }\n });\n wait = new Promise(function (resolve) {\n wait_dom.then(function () {\n load_placements().then(function (placements) {\n resolve(placements.filter(function (p) {\n return p != null;\n }));\n })[\"catch\"](function (err) {\n resolve([]);\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n });\n });\n });\n load = function load() {\n logger.debug(\"Loading placements manually\");\n load_placements(true)[\"catch\"](function (err) {\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n });\n };\n reload = function reload() {\n logger.debug(\"Reloading ad placement\");\n detectedKeywords = null;\n unload_placements();\n load_placements()[\"catch\"](function (err) {\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n });\n };\n}\n\n//# sourceURL=webpack://ethicalads/./index.js?");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Placement\", function() { return Placement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"check_dependencies\", function() { return check_dependencies; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"load_placements\", function() { return load_placements; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"unload_placements\", function() { return unload_placements; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"set_verbosity\", function() { return set_verbosity; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"wait\", function() { return wait; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"load\", function() { return load; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"reload\", function() { return reload; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"uplifted\", function() { return uplifted; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"detectedKeywords\", function() { return detectedKeywords; });\n/* harmony import */ var verge__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! verge */ \"./node_modules/verge/verge.js\");\n/* harmony import */ var verge__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(verge__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./styles.scss */ \"./styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_1__);\nfunction _typeof(o) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o; }, _typeof(o); }\nfunction _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }\nfunction _possibleConstructorReturn(t, e) { if (e && (\"object\" == _typeof(e) || \"function\" == typeof e)) return e; if (void 0 !== e) throw new TypeError(\"Derived constructors may only return object or undefined\"); return _assertThisInitialized(t); }\nfunction _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); return e; }\nfunction _inherits(t, e) { if (\"function\" != typeof e && null !== e) throw new TypeError(\"Super expression must either be null or a function\"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, \"prototype\", { writable: !1 }), e && _setPrototypeOf(t, e); }\nfunction _wrapNativeSuper(t) { var r = \"function\" == typeof Map ? new Map() : void 0; return _wrapNativeSuper = function _wrapNativeSuper(t) { if (null === t || !_isNativeFunction(t)) return t; if (\"function\" != typeof t) throw new TypeError(\"Super expression must either be null or a function\"); if (void 0 !== r) { if (r.has(t)) return r.get(t); r.set(t, Wrapper); } function Wrapper() { return _construct(t, arguments, _getPrototypeOf(this).constructor); } return Wrapper.prototype = Object.create(t.prototype, { constructor: { value: Wrapper, enumerable: !1, writable: !0, configurable: !0 } }), _setPrototypeOf(Wrapper, t); }, _wrapNativeSuper(t); }\nfunction _construct(t, e, r) { if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); var o = [null]; o.push.apply(o, e); var p = new (t.bind.apply(t, o))(); return r && _setPrototypeOf(p, r.prototype), p; }\nfunction _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }\nfunction _isNativeFunction(t) { try { return -1 !== Function.toString.call(t).indexOf(\"[native code]\"); } catch (n) { return \"function\" == typeof t; } }\nfunction _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }\nfunction _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }\nfunction _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }\nfunction _nonIterableSpread() { throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _unsupportedIterableToArray(r, a) { if (r) { if (\"string\" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return \"Object\" === t && r.constructor && (t = r.constructor.name), \"Map\" === t || \"Set\" === t ? Array.from(r) : \"Arguments\" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }\nfunction _iterableToArray(r) { if (\"undefined\" != typeof Symbol && null != r[Symbol.iterator] || null != r[\"@@iterator\"]) return Array.from(r); }\nfunction _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }\nfunction _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }\nfunction _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\"); }\nfunction _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }\nfunction _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", { writable: !1 }), e; }\nfunction _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == _typeof(i) ? i : i + \"\"; }\nfunction _toPrimitive(t, r) { if (\"object\" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != _typeof(i)) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n/* Ethical ad publisher JavaScript client\n *\n * Loads placement from Ethical Ad decision API. Searches for elements with\n * `ethical-ad` data binding attributes and uses these attributes to query the\n * decision API.\n *\n * This is native JavaScript, no JQuery. It uses the API JSONP interface to get\n * around CORS and related issues. A script is added with a callback on\n * `window`. The promise is rejected if there are errors with the request or the\n * response doesn't look correct.\n *\n * Currently, only two parameters are supported with the ad placement: publisher\n * id and the place type. All of this is determined by the server and this\n * client so far only renders the API return HTML.\n *\n * This can be loaded async. CSS styles are preloaded via webpack `style-loader`.\n * There is some potential for problems if CSP rules disallow inline\n * stylesheets, but webpack does allow for a hardcoded nonce.\n *\n * Usage:\n *\n * \n * \n */\n\n\n\nvar AD_CLIENT_VERSION = \"2.4.0-alpha\"; // Sent with the ad request\n\n// For local testing, set this\n// const AD_DECISION_URL = \"http://ethicaladserver:5000/api/v1/decision/\";\nvar AD_DECISION_URL = \"https://server.ethicalads.io/api/v1/decision/\";\nvar AD_TYPES_VERSION = 1; // Used with the ad type slugs\nvar ATTR_PREFIX = \"data-ea-\";\nvar ABP_DETECTION_PX = \"https://media.ethicalads.io/abp/px.gif\";\n\n// Verbosity and logging\n//\n// Set with:\n//\n// \nvar VERBOSITY = {\n quiet: 0,\n // Errors only\n normal: 1,\n // Warnings only (default)\n verbose: 2 // Debug messages\n};\nvar logger = {\n verbosity: VERBOSITY[\"normal\"],\n // Default\n debug: function debug(message) {\n if (this.verbosity >= VERBOSITY[\"verbose\"]) {\n var _console;\n for (var _len = arguments.length, params = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n params[_key - 1] = arguments[_key];\n }\n (_console = console).debug.apply(_console, [message].concat(params));\n }\n },\n info: function info(message) {\n if (this.verbosity >= VERBOSITY[\"verbose\"]) {\n var _console2;\n for (var _len2 = arguments.length, params = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n params[_key2 - 1] = arguments[_key2];\n }\n (_console2 = console).info.apply(_console2, [message].concat(params));\n }\n },\n warn: function warn(message) {\n if (this.verbosity >= VERBOSITY[\"normal\"]) {\n var _console3;\n for (var _len3 = arguments.length, params = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {\n params[_key3 - 1] = arguments[_key3];\n }\n (_console3 = console).warn.apply(_console3, [message].concat(params));\n }\n },\n error: function error(message) {\n if (this.verbosity >= VERBOSITY[\"quiet\"]) {\n var _console4;\n for (var _len4 = arguments.length, params = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {\n params[_key4 - 1] = arguments[_key4];\n }\n (_console4 = console).error.apply(_console4, [message].concat(params));\n }\n }\n};\n\n// Keywords and topics\n//\n// This allows us to categorize pages simply and have better content targeting.\n// Additional categorization can be done on the server side for pages\n// that request ads commonly but this quick and easy categorization\n// works decently well most of the time.\nvar KEYWORDS = new Set([\"2fa\", \"ai\", \"airflow\", \"algolia\", \"android\", \"angular\", \"angularjs\", \"ansible\", \"api\", \"appengine\", \"app-engine\", \"arangodb\", \"artificial-intelligence\", \"asp-net\", \"auth0\", \"authentication\", \"authorization\", \"aws\", \"azure\", \"babel\", \"backend\", \"backend-web\", \"bayes\", \"bayesian\", \"billing\", \"bitcoin\", \"blender\", \"blockchain\", \"celery\", \"chartjs\", \"chatbot\", \"chatbots\", \"chatgpt\", \"chatgpt3\", \"chatgpt4\", \"ci\", \"cicd\", \"ci-cd\", \"classifier\", \"cloud\", \"cloudformation\", \"cloud-formation\", \"cloudfront\", \"clustering\", \"cockroachdb\", \"commonjs\", \"computer-vision\", \"container\", \"containers\", \"continuousdeployment\", \"continuous-deployment\", \"continuousintegration\", \"continuous-integration\", \"cordova\", \"cplusplus\", \"cryptocurrency\", \"cryptography\", \"csharp\", \"c-sharp\", \"css\", \"cssinjs\", \"cuda\", \"cve\", \"cyber-attack\", \"cybersecurity\", \"cyber-security\", \"d3js\", \"dalle\", \"dall-e\", \"dao\", \"dapp\", \"dataanalytics\", \"data-analytics\", \"database\", \"datadog\", \"datalake\", \"data-lake\", \"datamesh\", \"data-mesh\", \"datascience\", \"data-science\", \"datascientist\", \"data-scientist\", \"data-visualization\", \"data-warehouse\", \"decryption\", \"deeplearning\", \"deep-learning\", \"deepreinforcement\", \"deep-reinforcement\", \"defi\", \"devops\", \"django\", \"djangorestframework\", \"django-rest-framework\", \"dnssec\", \"docker\", \"dockerhub\", \"docker-hub\", \"dockerizing\", \"dogecoin\", \"dotnet\", \"duckdb\", \"elasticsearch\", \"elastic-search\", \"emberjs\", \"erlang\", \"es6\", \"eslint\", \"ethereum\", \"express\", \"facedetection\", \"face-detection\", \"fiddler\", \"firebase\", \"firewall\", \"flask\", \"frontend\", \"frontend-web\", \"fsharp\", \"full-stack\", \"game\", \"gamedev\", \"gatsbyjs\", \"gcp\", \"gitguardian\", \"godot\", \"golang\", \"google-cloud\", \"gpt\", \"grafana\", \"grails\", \"graphql\", \"hacking\", \"haskell\", \"heroku\", \"hyperledger\", \"indiegame\", \"indie-game\", \"influxdb\", \"infosec\", \"invoice\", \"ionic\", \"ios\", \"ipfs\", \"iphone\", \"java\", \"javascript\", \"jenkins\", \"jfrog\", \"jinja\", \"jquery\", \"julia\", \"jupyter\", \"jvm\", \"kafka\", \"k-means-clustering\", \"kotlin\", \"kubernetes\", \"laravel\", \"lint\", \"linux\", \"llm\", \"llms\", \"log4j\", \"lucene\", \"machinelearning\", \"machine-learning\", \"mariadb\", \"matlab\", \"matplotlib\", \"maven\", \"metabase\", \"mfa\", \"midjourney\", \"minecraft\", \"mkdocs\", \"ml\", \"mobile\", \"model-training\", \"mongodb\", \"monitoring\", \"montecarlo\", \"monte-carlo\", \"mysql\", \"naivebayes\", \"naive-bayes\", \"neo4j\", \"neuralnet\", \"neural-net\", \"neural-nets\", \"neuralnetworks\", \"neural-networks\", \"newrelic\", \"new-relic\", \"nft\", \"nginx\", \"nlp\", \"node\", \"nodejs\", \"nosql\", \"numpy\", \"nuxt\", \"nuxtjs\", \"oauth\", \"obj-c\", \"objectdetection\", \"object-detection\", \"openai\", \"opencv-python-library\", \"openid\", \"openid-connect\", \"openjdk\", \"openshift\", \"openssl\", \"otp\", \"overfitting\", \"owasp\", \"pandas\", \"payment\", \"payments\", \"paypal\", \"penetration-test\", \"pentest\", \"perl\", \"phishing\", \"phonegap\", \"php\", \"pip\", \"postcss\", \"postgres\", \"postgresql\", \"privacy\", \"psf\", \"pwa\", \"pydata\", \"pygame\", \"pylint\", \"pypi\", \"pytest\", \"python\", \"pytorch\", \"pytorch3d\", \"rabbitmq\", \"rails\", \"rdbms\", \"rds\", \"react\", \"reactjs\", \"react-native\", \"redis\", \"redux\", \"regression\", \"regressionmodel\", \"regression-model\", \"reinforcement-learning\", \"rollbar\", \"ruby\", \"rust\", \"saltstack\", \"scala\", \"scikitlearn\", \"scikit-learn\", \"scipy\", \"scss\", \"security\", \"securityvulnerabilities\", \"security-vulnerabilities\", \"selenium\", \"selinux\", \"sencha\", \"sentiment-analysis\", \"sentry\", \"serverless\", \"single-page-application\", \"sklearn\", \"smartphone\", \"sms\", \"snowflake\", \"snyk\", \"solana\", \"solidity\", \"solr\", \"spa\", \"spacy\", \"sphinx\", \"sphinx-doc\", \"spring\", \"sql\", \"sqlite\", \"sqlserver\", \"sql-server\", \"stripe\", \"struts\", \"subscriptions\", \"svelte\", \"sveltejs\", \"swift\", \"symfony\", \"tableau\", \"tailwind\", \"tailwindcss\", \"tailwind-css\", \"tdd\", \"technical-writing\", \"tensor\", \"tensorflow\", \"tensorflowjs\", \"terraform\", \"test-driven-development\", \"testing\", \"tests\", \"textacy\", \"timescale\", \"timeseries\", \"training-data\", \"transformers\", \"travisci\", \"twilio\", \"two-factor-auth\", \"two-factor-authentication\", \"typescript\", \"ubuntu\", \"unittest\", \"unity\", \"vision-api\", \"visualization\", \"vue\", \"vuejs\", \"vuetify\", \"vuex\", \"vulnerability\", \"waf\", \"web3\", \"webapp-firewall\", \"webapplicationfirewall\", \"web-application-firewall\", \"webcomponents\", \"web-components\", \"webpack\", \"websecurity\", \"web-security\", \"werkzeug\", \"wireshark\", \"wsgi\", \"yarn\", \"zapier\"]);\n\n// Maximum number of words of a document to analyze looking for keywords\n// This is simply a check against taking too much time on very long documents\nvar MAX_WORDS_ANALYZED = 9999;\n\n// Max number of detected keywords to send\n// Lowering this number means that only major topics of the page get sent on long pages\nvar MAX_KEYWORDS = 3;\n\n// Minimum number of occurrences of a keyword to consider it\nvar MIN_KEYWORD_OCCURRENCES = 2;\n\n// Time between checking whether the ad is in the viewport to count the time viewed\n// Time viewed is an important advertiser metric\nvar VIEW_TIME_INTERVAL = 1; // seconds\nvar VIEW_TIME_MAX = 5 * 60; // seconds\n\n// In-viewport fudge factor\n// A fudge factor of ~3 is needed for the case where the ad\n// is hidden off the side of the screen by a sliding sidebar\n// For example, if the right side of the ad is at x=0\n// or the left side of the ad is at the right side of the viewport\nvar VIEWPORT_FUDGE_FACTOR = -3; // px\n\n// An ad may be rotated if it has been visible for sufficient time\n// And there is user interaction such as a hashchange or visibilitychange.\n// We rotate no more than the maximum number of rotations.\n// Loading the ad the first time counts as the first rotation.\nvar MIN_VIEW_TIME_ROTATION_DURATION = 45; // seconds\nvar MAX_ROTATIONS = 3;\n\n// Enable ad rotation on hash change (intra-site nav)\nvar HASHCHANGE_ROTATION_ENABLE = true;\n\n// Seconds after a tab comes back into focus to rotate an ad.\nvar VISIBILITYCHANGE_ROTATION_ENABLE = true;\nvar VISIBILITYCHANGE_ROTATION_DELAY = 3; // seconds\n\n/* Placement object to query decision API and return an Element node\n *\n * @param {string} publisher - Publisher ID\n * @param {string} ad_type - Placement ad type id\n * @param {Element} target - Target element\n * @param {Object} options - Various options for configuring the placement such as:\n keywords, styles, campaign_types, load_manually, force_ad, force_campaign\n */\nvar Placement = /*#__PURE__*/function () {\n function Placement(publisher, ad_type, target, options) {\n _classCallCheck(this, Placement);\n this.publisher = publisher;\n this.ad_type = ad_type;\n this.target = target;\n\n // Options\n this.options = options;\n this.style = options.style;\n this.keywords = options.keywords || [];\n this.load_manually = options.load_manually;\n this.force_ad = options.force_ad;\n this.force_campaign = options.force_campaign;\n this.campaign_types = options.campaign_types || [];\n if (!this.campaign_types.length) {\n this.campaign_types = [\"paid\", \"publisher-house\", \"community\", \"house\"];\n }\n this.priority = options.priority || null;\n this.div_id = target.id || \"ad_\" + Date.now() + \"_\" + Math.floor(Math.random() * 1000000000);\n\n // Initialized and will be used in the future\n this.view_time = 0;\n this.view_time_sent = false; // true once the view time is sent to the server\n this.response = null;\n this.tab_hidden = false;\n this.rotations = 1;\n this.index = null;\n }\n\n /* Create a placement from an element\n *\n * Returns null if the placement is already loaded.\n *\n * @static\n * @param {Element} element - Load placement and append to this Element\n * @returns {Placement}\n */\n return _createClass(Placement, [{\n key: \"load\",\n value:\n /* Transforms target element into a placement\n *\n * This method organizes all of the operations to transform the placement\n * configuration wrapper `div` into an ad placement -- including starting the\n * API transaction, displaying the ad element,\n * and handling the viewport detection.\n *\n * @returns {Promise}\n */\n function load() {\n var _this = this;\n var fetchPromise = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;\n // Detect the keywords\n this.keywords = this.keywords.concat(this.detectKeywords());\n fetchPromise = fetchPromise || this.fetch();\n return fetchPromise.then(function (element) {\n if (element === undefined) {\n throw new EthicalAdsWarning(\"Ad decision request blocked or invalid.\");\n }\n if (!element) {\n throw new EthicalAdsWarning(\"No ads to show.\");\n }\n\n // Add `loaded` class, signifying that the CSS styles should finally be\n // applied to the target element.\n var classes = _this.target.className || \"\";\n classes += \" loaded\";\n _this.target.className = classes.trim();\n\n // Make this element the only child element of the target element\n while (_this.target.firstChild) {\n _this.target.removeChild(_this.target.firstChild);\n }\n\n // Apply any styles based on the specified styling\n _this.applyStyles(element);\n _this.target.appendChild(element);\n return _this;\n }).then(function (placement) {\n // Detect when the ad is in the viewport\n // Add the view pixel to the DOM to count the view\n // Also count the time the ad is in view\n // this will be sent before the page/tab is closed or navigated away\n\n var viewport_detection = setInterval(function (element) {\n if (placement.inViewport(element)) {\n // This ad was seen!\n var pixel = document.createElement(\"img\");\n pixel.src = placement.response.view_url;\n if (uplifted) {\n pixel.src += \"?uplift=true\";\n }\n pixel.className = \"ea-pixel\";\n element.appendChild(pixel);\n clearInterval(viewport_detection);\n }\n }, 100, placement.target);\n placement.view_time_counter = setInterval(function (element) {\n if (placement.tab_hidden === false && placement.inViewport(element)) {\n // Increment the ad's time in view counter\n placement.view_time += VIEW_TIME_INTERVAL;\n if (placement.view_time >= VIEW_TIME_MAX) {\n clearInterval(placement.view_time_counter);\n }\n }\n }, VIEW_TIME_INTERVAL * 1000, placement.target);\n placement.hashchange_listener = function () {\n if (placement.canRotate()) {\n placement.sendViewTime();\n placement.rotate();\n }\n };\n if (HASHCHANGE_ROTATION_ENABLE) {\n window.addEventListener(\"hashchange\", placement.hashchange_listener);\n }\n\n // Listens to the window visibility\n // Rotates the ad when the window comes back into focus if\n // other conditions (minimum view time, under max rotations)\n // are met.\n // When the tab loses focus, send the view time to the server.\n placement.visibilitychange_listener = function () {\n if (document.visibilityState === \"hidden\" || document.visibilityState === \"unloaded\") {\n // Check if the tab loses focus/is closed or the browser/app is minimized/closed\n // In that case, no longer count further time that the ad is in view\n // Send the time the ad was viewed to the server\n placement.tab_hidden = true;\n placement.sendViewTime();\n }\n\n // This tab was invisible and has come back into focus\n // Trigger an ad rotation\n if (placement.tab_hidden === true && document.visibilityState === \"visible\") {\n placement.tab_hidden = false;\n if (placement.canRotate()) {\n placement.sendViewTime(); // Should already be sent, but just in case\n setTimeout(function () {\n placement.rotate();\n }, VISIBILITYCHANGE_ROTATION_DELAY * 1000);\n }\n }\n };\n if (VISIBILITYCHANGE_ROTATION_ENABLE) {\n document.addEventListener(\"visibilitychange\", placement.visibilitychange_listener);\n }\n return _this;\n });\n }\n\n /* Clears all the placement's event listeners */\n }, {\n key: \"clearListeners\",\n value: function clearListeners() {\n if (this.view_time_counter) {\n clearInterval(this.view_time_counter);\n }\n if (this.hashchange_listener && HASHCHANGE_ROTATION_ENABLE) {\n window.removeEventListener(\"hashchange\", this.hashchange_listener);\n }\n if (this.visibilitychange_listener && VISIBILITYCHANGE_ROTATION_ENABLE) {\n document.removeEventListener(\"visibilitychange\", this.visibilitychange_listener);\n }\n }\n\n /* Returns whether the conditions to rotate are met\n *\n * @returns {boolean} True if the placement can rotate\n */\n }, {\n key: \"canRotate\",\n value: function canRotate() {\n if (!this.inViewport(this.target) || this.view_time < MIN_VIEW_TIME_ROTATION_DURATION || this.rotations >= MAX_ROTATIONS) {\n return false;\n }\n return true;\n }\n\n /* Reloads the placement with a new ad (if applicable)\n *\n * @returns {Promise}\n */\n }, {\n key: \"rotate\",\n value: function rotate() {\n if (!this.canRotate()) {\n return;\n }\n logger.debug(\"Rotating ad\");\n this.clearListeners();\n this.view_time = 0;\n this.view_time_sent = false;\n this.response = null;\n this.tab_hidden = false;\n this.rotations += 1;\n return this.load();\n }\n\n /* Returns whether the ad is visible in the viewport\n *\n * @param {Element} element - The ad element\n * @returns {boolean} True if the ad is loaded and visible in the viewport\n * (including the tab being focused and not minimized) and returns false otherwise.\n */\n }, {\n key: \"inViewport\",\n value: function inViewport(element) {\n if (this.response && this.response.view_url && verge__WEBPACK_IMPORTED_MODULE_0___default.a.inViewport(element, VIEWPORT_FUDGE_FACTOR) && document.visibilityState === \"visible\") {\n return true;\n }\n return false;\n }\n\n /* Get placement data from decision API\n *\n * @returns {Promise} Resolves with an Element converted from an HTML\n * string from API response. Can also be null, indicating a noop action.\n */\n }, {\n key: \"fetch\",\n value: function fetch() {\n var _this2 = this;\n // Make sure callbacks don't collide even with multiple placements\n var callback = \"ad_\" + Date.now() + \"_\" + Math.floor(Math.random() * 1000000);\n var div_id = this.div_id;\n\n // There's no hard maximum on URL lengths (all of these get added to the query params)\n // but ideally we want to keep our URLs below ~2k which should work basically everywhere\n var params = {\n publisher: this.publisher,\n ad_types: this.ad_type,\n div_ids: div_id,\n callback: callback,\n keywords: this.keywords.join(\"|\"),\n campaign_types: this.campaign_types.join(\"|\"),\n format: \"jsonp\",\n client_version: AD_CLIENT_VERSION,\n placement_index: this.index,\n // location.href includes query params (possibly sensitive) and fragments (unnecessary)\n url: (window.location.origin + window.location.pathname).slice(0, 256)\n };\n if (this.force_ad) {\n params[\"force_ad\"] = this.force_ad;\n }\n if (this.force_campaign) {\n params[\"force_campaign\"] = this.force_campaign;\n }\n if (this.rotations > 1) {\n params[\"rotations\"] = this.rotations;\n }\n var url_params = new URLSearchParams(params);\n var url = new URL(AD_DECISION_URL + \"?\" + url_params.toString());\n return new Promise(function (resolve, reject) {\n window[callback] = function (response) {\n if (response && response.html && response.view_url) {\n _this2.response = response;\n var node_convert = document.createElement(\"div\");\n node_convert.innerHTML = response.html;\n return resolve(node_convert.firstChild);\n } else {\n // No ad to show for this targeting/publisher\n return resolve(null);\n }\n };\n var script = document.createElement(\"script\");\n script.src = url;\n script.type = \"text/javascript\";\n script.async = true;\n script.addEventListener(\"error\", function (err) {\n // There was a problem loading this request, likely this was blocked by\n // an ad blocker. We'll resolve with an empty response instead of\n // throwing an error.\n return resolve();\n });\n document.getElementsByTagName(\"head\")[0].appendChild(script);\n });\n }\n\n /* Fetch multiple grouped placements from the decision API\n *\n * @param {Array} placements - Placements to fetch\n */\n }, {\n key: \"sendViewTime\",\n value:\n /* Sends the view time of the ad to the server\n */\n function sendViewTime() {\n if (this.view_time <= 0 || this.view_time_sent || !this.response || !this.response.view_time_url) return;\n var pixel = document.createElement(\"img\");\n pixel.src = this.response.view_time_url + \"?view_time=\" + this.view_time;\n pixel.className = \"ea-pixel\";\n this.target.appendChild(pixel);\n this.view_time_sent = true;\n }\n\n /* Detect whether this ad is \"uplifted\" meaning allowed by ABP's Acceptable Ads list\n *\n * Calls the provided callback passing a boolean whether this ad is uplifted.\n * We need this data to provide back to the AcceptableAds folks.\n *\n * This code comes directly from Eyeo/AdblockPlus team to measure Acceptable Ads.\n *\n * @static\n * @param {string} px - A URL of a pixel to test\n * @param {function) callback - A callback to call when finished\n */\n }, {\n key: \"detectABP\",\n value: function detectABP(px, callback) {\n var detected = false;\n var checksRemain = 2;\n var error1 = false;\n var error2 = false;\n if (typeof callback != \"function\") return;\n px += \"?ch=*&rn=*\";\n function beforeCheck(callback, timeout) {\n if (checksRemain == 0 || timeout > 1e3) callback(checksRemain == 0 && detected);else setTimeout(function () {\n beforeCheck(callback, timeout * 2);\n }, timeout * 2);\n }\n function checkImages() {\n if (--checksRemain) return;\n detected = !error1 && error2;\n }\n var random = Math.random() * 11;\n var img1 = new Image();\n img1.onload = checkImages;\n img1.onerror = function () {\n error1 = true;\n checkImages();\n };\n img1.src = px.replace(/\\*/, 1).replace(/\\*/, random);\n var img2 = new Image();\n img2.onload = checkImages;\n img2.onerror = function () {\n error2 = true;\n checkImages();\n };\n img2.src = px.replace(/\\*/, 2).replace(/\\*/, random);\n beforeCheck(callback, 250);\n }\n\n /* Returns an array of keywords (strings) found on the page\n *\n * @returns {Array[string]} Advertising keywords found on the page\n */\n }, {\n key: \"detectKeywords\",\n value: function detectKeywords() {\n // Return previously detected keywords\n // If this code has already run.\n // Note: if there are \"no\" keywords (an empty list) this is still true\n if (detectedKeywords) return detectedKeywords;\n var keywordHist = {}; // Keywords found => count of keyword\n var mainContent = document.querySelector(\"[role='main']\") || document.querySelector(\"main\") || document.querySelector(\"body\");\n var words = mainContent.textContent.split(/\\s+/);\n var wordTrimmer = /^[\\('\"]?(.*?)[,\\.\\?\\!:;\\)'\"]?$/g;\n for (var x = 0; x < words.length && x < MAX_WORDS_ANALYZED; x++) {\n // Remove certain punctuation from beginning and end of the word\n var word = words[x].replace(wordTrimmer, \"$1\").toLowerCase();\n if (KEYWORDS.has(word)) {\n keywordHist[word] = (keywordHist[word] || 0) + 1;\n }\n }\n\n // Sort the hist with the most common items first\n // Grab only the MAX_KEYWORDS most common\n var keywords = Object.entries(keywordHist).filter(\n // Only consider a keyword with at least this many occurrences\n function (a) {\n return a[1] >= MIN_KEYWORD_OCCURRENCES;\n }).sort(function (a, b) {\n if (a[1] > b[1]) return -1;\n if (a[1] < b[1]) return 1;\n return 0;\n }).slice(0, MAX_KEYWORDS).map(function (x) {\n return x[0];\n });\n detectedKeywords = keywords;\n return keywords;\n }\n\n /* Apply custom styles based on data-ea-style\n *\n */\n }, {\n key: \"applyStyles\",\n value: function applyStyles(element) {\n // Stickybox: https://ethical-ad-client.readthedocs.io/en/latest/#stickybox\n if (this.style === \"stickybox\") {\n var hideButton = document.createElement(\"div\");\n hideButton.setAttribute(\"class\", \"ea-stickybox-hide\");\n hideButton.innerHTML = \"×\";\n hideButton.addEventListener(\"click\", function () {\n document.querySelector(\"[data-ea-publisher]\").remove();\n });\n element.appendChild(hideButton);\n }\n\n // FixedFooter: https://ethical-ad-client.readthedocs.io/en/latest/#fixedfooter\n if (this.style === \"fixedfooter\") {\n //element.querySelector('.ea-callout a').remove();\n\n var container = document.createElement(\"div\");\n container.setAttribute(\"class\", \"ea-fixedfooter-hide\");\n element.appendChild(container);\n var _hideButton = document.createElement(\"span\");\n _hideButton.append(\"Close Ad\");\n _hideButton.addEventListener(\"click\", function () {\n document.querySelector(\"[data-ea-publisher]\").remove();\n });\n container.appendChild(_hideButton);\n }\n\n // FixedHeader: https://ethical-ad-client.readthedocs.io/en/latest/#fixedheader\n // No special elements required\n }\n }], [{\n key: \"from_element\",\n value: function from_element(element) {\n // Get attributes from DOM node\n var publisher = element.getAttribute(ATTR_PREFIX + \"publisher\");\n if (!publisher) {\n logger.debug(\"Missing publisher value. Skipping...\");\n return null;\n }\n var ad_type = element.getAttribute(ATTR_PREFIX + \"type\");\n if (!ad_type) {\n ad_type = \"image\";\n element.setAttribute(ATTR_PREFIX + \"type\", \"image\");\n }\n var keywords = (element.getAttribute(ATTR_PREFIX + \"keywords\") || \"\").split(\"|\").filter(function (word) {\n return word.length > 1;\n });\n var campaign_types = (element.getAttribute(ATTR_PREFIX + \"campaign-types\") || \"\").split(\"|\").filter(function (word) {\n return word.length > 1;\n });\n var load_manually = element.getAttribute(ATTR_PREFIX + \"manual\") === \"true\";\n var style = element.getAttribute(ATTR_PREFIX + \"style\");\n var force_ad = element.getAttribute(ATTR_PREFIX + \"force-ad\");\n var force_campaign = element.getAttribute(ATTR_PREFIX + \"force-campaign\");\n var priorityAttr = element.getAttribute(ATTR_PREFIX + \"priority\");\n var priority;\n if (priorityAttr !== null && priorityAttr !== \"\") {\n var parsedPriority = parseInt(priorityAttr, 10);\n if (!Number.isNaN(parsedPriority) && parsedPriority >= 1 && parsedPriority <= 10) {\n priority = parsedPriority;\n } else {\n logger.warn(\"EthicalAd: Invalid numerical priority '%s' provided; ignoring.\", priorityAttr);\n }\n }\n\n // Add version to ad type to verison the HTML return\n if (ad_type === \"image\" || ad_type === \"text\") {\n ad_type += \"-v\" + AD_TYPES_VERSION;\n }\n var classes = (element.className || \"\").split(\" \");\n if (classes.indexOf(\"loaded\") >= 0) {\n logger.warn(\"EthicalAd already loaded.\");\n return null;\n }\n\n // Note: this attribute value *must* contain a unit (eg. '200px')\n var placementBottom = element.getAttribute(ATTR_PREFIX + \"placement-bottom\");\n if (placementBottom) {\n element.style.setProperty(\"bottom\", placementBottom);\n }\n return new Placement(publisher, ad_type, element, {\n keywords: keywords,\n style: style,\n campaign_types: campaign_types,\n load_manually: load_manually,\n force_ad: force_ad,\n force_campaign: force_campaign,\n priority: priority\n });\n }\n }, {\n key: \"loadGroup\",\n value: function loadGroup(placements) {\n var _placements$find, _placements$find2;\n if (!placements || !placements.length) return Promise.resolve([]);\n var publisher = placements[0].publisher;\n\n // Make sure all placements are from the same publisher\n var all_same_publisher = placements.every(function (p) {\n return p.publisher === publisher;\n });\n if (!all_same_publisher) {\n throw new Error(\"Multiple ad placements with different publishers are not allowed.\");\n }\n var callback = \"ad_\" + Date.now() + \"_\" + Math.floor(Math.random() * 1000000000);\n var group_keywords = [];\n placements.forEach(function (p) {\n // Only detect keywords if they haven't already been detected\n if (!Array.isArray(p.keywords)) {\n p.keywords = [];\n }\n if (p.keywords.length === 0) {\n p.keywords = p.keywords.concat(p.detectKeywords());\n }\n group_keywords = group_keywords.concat(p.keywords);\n });\n group_keywords = _toConsumableArray(new Set(group_keywords));\n var params = {\n publisher: publisher,\n ad_types: placements.map(function (p) {\n return p.ad_type;\n }).join(\"|\"),\n div_ids: placements.map(function (p) {\n return p.div_id;\n }).join(\"|\"),\n priorities: placements.map(function (p) {\n return p.priority;\n }).join(\"|\"),\n callback: callback,\n keywords: group_keywords.join(\"|\"),\n campaign_types: _toConsumableArray(new Set(placements.reduce(function (acc, p) {\n return acc.concat(p.campaign_types);\n }, []))).join(\"|\"),\n format: \"jsonp\",\n client_version: AD_CLIENT_VERSION,\n url: (window.location.origin + window.location.pathname).slice(0, 256)\n // placement_index is not used for grouped/prioritized placements\n };\n var force_ad = (_placements$find = placements.find(function (p) {\n return p.force_ad;\n })) === null || _placements$find === void 0 ? void 0 : _placements$find.force_ad;\n if (force_ad) params[\"force_ad\"] = force_ad;\n var force_campaign = (_placements$find2 = placements.find(function (p) {\n return p.force_campaign;\n })) === null || _placements$find2 === void 0 ? void 0 : _placements$find2.force_campaign;\n if (force_campaign) params[\"force_campaign\"] = force_campaign;\n var max_rotations = Math.max.apply(Math, _toConsumableArray(placements.map(function (p) {\n return p.rotations;\n })));\n if (max_rotations > 1) params[\"rotations\"] = max_rotations;\n var url_params = new URLSearchParams(params);\n var url = new URL(AD_DECISION_URL + \"?\" + url_params.toString());\n var promise = new Promise(function (resolve, reject) {\n window[callback] = function (response) {\n resolve(response);\n };\n var script = document.createElement(\"script\");\n script.src = url;\n script.type = \"text/javascript\";\n script.async = true;\n script.addEventListener(\"error\", function (err) {\n resolve();\n });\n document.getElementsByTagName(\"head\")[0].appendChild(script);\n });\n return Promise.all(placements.map(function (placement, idx) {\n var fetchPromise = promise.then(function (response) {\n var is_winner = false;\n if (response && response.html && response.view_url) {\n if (response.div_id) {\n is_winner = response.div_id === placement.div_id;\n } else {\n // This should only happen if there are no ads for this request\n // In this case, we'll assign the first placement to be the winner\n is_winner = idx === 0;\n }\n }\n if (is_winner) {\n placement.response = response;\n var node_convert = document.createElement(\"div\");\n node_convert.innerHTML = response.html;\n return node_convert.firstChild;\n } else {\n return null;\n }\n });\n return placement.load(fetchPromise)[\"catch\"](function (err) {\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n return null;\n });\n }));\n }\n }]);\n}();\n\n/* Detects whether the browser supports the necessary JS APIs to support the ad client\n *\n * Generally we support recent versions of evergreen browsers (Chrome, Firefox, Safari, Edge)\n * but we no longer support IE11.\n *\n * @returns {boolean} true if all dependencies met and false otherwise\n */\nfunction check_dependencies() {\n if (!Object.entries || !window.URL || !window.URLSearchParams || !window.Promise) {\n logger.error(\"Browser does not meet ethical ad client dependencies. Not showing ads\");\n return false;\n }\n return true;\n}\n\n/* Find all placement DOM elements and hot load HTML as child nodes\n *\n * @param {boolean} force_load - load placements even if they are set to load manually\n * @returns {Promise<[Placement]>} Resolves to a list of Placement instances\n */\nfunction load_placements() {\n var force_load = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n // Find all elements matching required data binding attribute.\n var node_list = document.querySelectorAll(\"[\" + ATTR_PREFIX + \"publisher]\");\n var elements = Array.prototype.slice.call(node_list);\n var publishers = new Set();\n elements.forEach(function (el) {\n var pub = el.getAttribute(ATTR_PREFIX + \"publisher\");\n if (pub) publishers.add(pub);\n });\n if (publishers.size > 1) {\n throw new Error(\"Multiple ad placements with different publishers are not allowed.\");\n }\n if (elements.length === 0) {\n logger.warn(\"No ad placements found.\");\n }\n var placements = elements.map(function (element, index) {\n var placement = Placement.from_element(element);\n if (!placement) return null;\n placement.index = index;\n return placement;\n });\n\n // Run AcceptableAds detection code once for the first valid placement\n var first_placement = placements.find(function (p) {\n return p !== null;\n });\n if (first_placement && !force_load) {\n first_placement.detectABP(ABP_DETECTION_PX, function (usesABP) {\n uplifted = usesABP;\n if (usesABP) {\n logger.debug(\"Acceptable Ads enabled. Thanks for allowing our non-tracking ads :)\");\n }\n });\n }\n\n // Group prioritized placements\n var has_priority = placements.some(function (p) {\n return p && p.priority !== null;\n });\n if (has_priority) {\n var priority_group = [];\n placements.forEach(function (placement) {\n if (placement && (force_load || !placement.load_manually)) {\n if (placement.priority === null) {\n // Set default priority for non-prioritized placements\n placement.priority = 1;\n }\n priority_group.push(placement);\n }\n });\n if (priority_group.length > 0) {\n return Placement.loadGroup(priority_group);\n } else {\n return Promise.resolve([]);\n }\n } else {\n // Create main promise. Iterator `all()` Promise will surround array of\n // placements.\n return Promise.all(placements.map(function (placement) {\n if (placement && (force_load || !placement.load_manually)) {\n return placement.load()[\"catch\"](function (err) {\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n return null;\n });\n } else {\n return null;\n }\n }));\n }\n}\nfunction unload_placements() {\n var node_list = document.querySelectorAll(\"[\" + ATTR_PREFIX + \"publisher]\");\n var elements = Array.prototype.slice.call(node_list);\n elements.forEach(function (div) {\n div.innerHTML = \"\";\n div.classList.remove(\"loaded\");\n });\n}\nfunction set_verbosity() {\n var element = document.querySelector(\"[\" + ATTR_PREFIX + \"publisher]\");\n if (element) {\n var user_verbosity = element.getAttribute(ATTR_PREFIX + \"verbosity\");\n if (VERBOSITY.hasOwnProperty(user_verbosity)) {\n logger.verbosity = VERBOSITY[user_verbosity];\n }\n }\n}\n\n// An error class that we will not surface to clients normally.\nvar EthicalAdsWarning = /*#__PURE__*/function (_Error) {\n function EthicalAdsWarning() {\n _classCallCheck(this, EthicalAdsWarning);\n return _callSuper(this, EthicalAdsWarning, arguments);\n }\n _inherits(EthicalAdsWarning, _Error);\n return _createClass(EthicalAdsWarning);\n}(/*#__PURE__*/_wrapNativeSuper(Error));\n/* Wrapping Promise to allow for handling of errors by user\n *\n * This promise currently does not reject on error as this will emit a console\n * warning if the user hasn't added a promise rejection handler (which is most\n * cases).\n *\n * This promise resolves to an aray of Placement instances, or an empty list if\n * there was any error configuring the placements.\n *\n * For example, to perform an action when no placements are loaded:\n *\n * \n *\n * @type {Promise<[Placement]>}\n */\nvar wait;\n\n/* Loading placements manually rather than the normal way\n *\n * \n * \n *\n * @type function\n */\nvar load;\n\n/* Reloading placements. Used by SPAs.\n * @type function\n */\nvar reload;\n\n/* Whether this ad impression is attributed to being on the Acceptable Ads list.\n * @type boolean\n */\nvar uplifted = false;\n\n/* Keywords detected on the page\n * @type {Array[string]}\n */\nvar detectedKeywords = null;\n\n/* If importing this as a module, do not automatically process DOM and fetch the\n * ad placement. Only do this if using the module directly, from a `script`\n * element. This will allow for future extension and packaging as a module.\n *\n * This also replicates JQuery `$(document).ready()`, with added protection for\n * usage of `async` -- the DOM ready event can fire before the script is loaded..\n */\nif (window.ethicalads) {\n // Always display this warning regardless of log level\n // This is a code mistake by publishers and should be caught right away.\n console.warn(\"Double-loading the EthicalAds client. Use reload() instead. https://ethical-ad-client.readthedocs.io/en/latest/#single-page-apps\");\n}\nif (check_dependencies()) {\n // Set the client verbosity\n set_verbosity();\n var wait_dom = new Promise(function (resolve) {\n if (document.readyState === \"interactive\" || document.readyState === \"complete\") {\n return resolve();\n } else {\n document.addEventListener(\"DOMContentLoaded\", function () {\n resolve();\n }, {\n capture: true,\n once: true,\n passive: true\n });\n }\n });\n wait = new Promise(function (resolve) {\n wait_dom.then(function () {\n load_placements().then(function (placements) {\n resolve(placements.filter(function (p) {\n return p != null;\n }));\n })[\"catch\"](function (err) {\n resolve([]);\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n });\n });\n });\n load = function load() {\n logger.debug(\"Loading placements manually\");\n load_placements(true)[\"catch\"](function (err) {\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n });\n };\n reload = function reload() {\n logger.debug(\"Reloading ad placement\");\n detectedKeywords = null;\n unload_placements();\n load_placements()[\"catch\"](function (err) {\n if (err instanceof EthicalAdsWarning) {\n logger.warn(err.message);\n } else {\n logger.error(err.message);\n }\n });\n };\n}\n\n//# sourceURL=webpack://ethicalads/./index.js?");
/***/ }),
diff --git a/dist/ethicalads.min.js b/dist/ethicalads.min.js
index eecfea3..df68a63 100644
--- a/dist/ethicalads.min.js
+++ b/dist/ethicalads.min.js
@@ -1 +1 @@
-var ethicalads=function(e){var a={};function t(o){if(a[o])return a[o].exports;var r=a[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}return t.m=e,t.c=a,t.d=function(e,a,o){t.o(e,a)||Object.defineProperty(e,a,{enumerable:!0,get:o})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,a){if(1&a&&(e=t(e)),8&a)return e;if(4&a&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(t.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&a&&"string"!=typeof e)for(var r in e)t.d(o,r,function(a){return e[a]}.bind(null,r));return o},t.n=function(e){var a=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(a,"a",a),a},t.o=function(e,a){return Object.prototype.hasOwnProperty.call(e,a)},t.p="",t(t.s=1)}([function(e,a,t){var o,r;o=this,r=function(){var e={},a="undefined"!=typeof window&&window,t="undefined"!=typeof document&&document,o=t&&t.documentElement,r=a.matchMedia||a.msMatchMedia,i=r?function(e){return!!r.call(a,e).matches}:function(){return!1},n=e.viewportW=function(){var e=o.clientWidth,t=a.innerWidth;return e=0&&t.left<=n()},e.inY=function(e,a){var t=c(e,a);return!!t&&t.bottom>=0&&t.top<=l()},e.inViewport=function(e,a){var t=c(e,a);return!!t&&t.bottom>=0&&t.right>=0&&t.top<=l()&&t.left<=n()},e},e.exports?e.exports=r():o.verge=r()},function(e,a,t){"use strict";t.r(a),t.d(a,"Placement",(function(){return v})),t.d(a,"check_dependencies",(function(){return x})),t.d(a,"load_placements",(function(){return w})),t.d(a,"unload_placements",(function(){return k})),t.d(a,"set_verbosity",(function(){return _})),t.d(a,"wait",(function(){return j})),t.d(a,"load",(function(){return S})),t.d(a,"reload",(function(){return z})),t.d(a,"uplifted",(function(){return E})),t.d(a,"detectedKeywords",(function(){return O}));var o=t(0),r=t.n(o);t(2);function i(e){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function n(e,a,t){return a=u(a),function(e,a){if(a&&("object"==i(a)||"function"==typeof a))return a;if(void 0!==a)throw new TypeError("Derived constructors may only return object or undefined");return function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e)}(e,c()?Reflect.construct(a,t||[],u(e).constructor):a.apply(e,t))}function l(e){var a="function"==typeof Map?new Map:void 0;return(l=function(e){if(null===e||!function(e){try{return-1!==Function.toString.call(e).indexOf("[native code]")}catch(a){return"function"==typeof e}}(e))return e;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==a){if(a.has(e))return a.get(e);a.set(e,t)}function t(){return d(e,arguments,u(this).constructor)}return t.prototype=Object.create(e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),s(t,e)})(e)}function d(e,a,t){if(c())return Reflect.construct.apply(null,arguments);var o=[null];o.push.apply(o,a);var r=new(e.bind.apply(e,o));return t&&s(r,t.prototype),r}function c(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){})))}catch(e){}return(c=function(){return!!e})()}function s(e,a){return(s=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,a){return e.__proto__=a,e})(e,a)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function p(e,a){if(!(e instanceof a))throw new TypeError("Cannot call a class as a function")}function f(e,a){for(var t=0;t=g.verbose){for(var a,t=arguments.length,o=new Array(t>1?t-1:0),r=1;r=g.verbose){for(var a,t=arguments.length,o=new Array(t>1?t-1:0),r=1;r=g.normal){for(var a,t=arguments.length,o=new Array(t>1?t-1:0),r=1;r=g.quiet){for(var a,t=arguments.length,o=new Array(t>1?t-1:0),r=1;r=300&&clearInterval(a.view_time_counter))}),1e3,a.target),a.hashchange_listener=function(){a.canRotate()&&(a.sendViewTime(),a.rotate())},window.addEventListener("hashchange",a.hashchange_listener),a.visibilitychange_listener=function(){"hidden"!==document.visibilityState&&"unloaded"!==document.visibilityState||(a.tab_hidden=!0,a.sendViewTime()),!0===a.tab_hidden&&"visible"===document.visibilityState&&(a.tab_hidden=!1,a.canRotate()&&(a.sendViewTime(),setTimeout((function(){a.rotate()}),3e3)))},document.addEventListener("visibilitychange",a.visibilitychange_listener),e}))}},{key:"clearListeners",value:function(){this.view_time_counter&&clearInterval(this.view_time_counter),this.hashchange_listener&&window.removeEventListener("hashchange",this.hashchange_listener),this.visibilitychange_listener&&document.removeEventListener("visibilitychange",this.visibilitychange_listener)}},{key:"canRotate",value:function(){return!(!this.inViewport(this.target)||this.view_time<45||this.rotations>=3)}},{key:"rotate",value:function(){if(this.canRotate())return this.clearListeners(),this.view_time=0,this.view_time_sent=!1,this.response=null,this.tab_hidden=!1,this.rotations+=1,this.load()}},{key:"inViewport",value:function(e){return!!(this.response&&this.response.view_url&&r.a.inViewport(e,-3)&&"visible"===document.visibilityState)}},{key:"fetch",value:function(){var e=this,a="ad_"+Date.now()+"_"+Math.floor(1e6*Math.random()),t=a;this.target.id&&(t=this.target.id);var o={publisher:this.publisher,ad_types:this.ad_type,div_ids:t,callback:a,keywords:this.keywords.join("|"),campaign_types:this.campaign_types.join("|"),format:"jsonp",client_version:"2.3.0",placement_index:this.index,url:(window.location.origin+window.location.pathname).slice(0,256)};this.force_ad&&(o.force_ad=this.force_ad),this.force_campaign&&(o.force_campaign=this.force_campaign),this.rotations>1&&(o.rotations=this.rotations);var r=new URLSearchParams(o),i=new URL("https://server.ethicalads.io/api/v1/decision/?"+r.toString());return new Promise((function(t,o){window[a]=function(a){if(a&&a.html&&a.view_url){e.response=a;var o=document.createElement("div");return o.innerHTML=a.html,t(o.firstChild)}return t(null)};var r=document.createElement("script");r.src=i,r.type="text/javascript",r.async=!0,r.addEventListener("error",(function(e){return t()})),document.getElementsByTagName("head")[0].appendChild(r)}))}},{key:"sendViewTime",value:function(){if(!(this.view_time<=0||this.view_time_sent)&&this.response&&this.response.view_time_url){var e=document.createElement("img");e.src=this.response.view_time_url+"?view_time="+this.view_time,e.className="ea-pixel",this.target.appendChild(e),this.view_time_sent=!0}}},{key:"detectABP",value:function(e,a){var t=!1,o=2,r=!1,i=!1;if("function"==typeof a){e+="?ch=*&rn=*";var n=11*Math.random(),l=new Image;l.onload=c,l.onerror=function(){r=!0,c()},l.src=e.replace(/\*/,1).replace(/\*/,n);var d=new Image;d.onload=c,d.onerror=function(){i=!0,c()},d.src=e.replace(/\*/,2).replace(/\*/,n),function e(a,r){0==o||r>1e3?a(0==o&&t):setTimeout((function(){e(a,2*r)}),2*r)}(a,250)}function c(){--o||(t=!r&&i)}}},{key:"detectKeywords",value:function(){if(O)return O;for(var e={},a=(document.querySelector("[role='main']")||document.querySelector("main")||document.querySelector("body")).textContent.split(/\s+/),t=/^[\('"]?(.*?)[,\.\?\!:;\)'"]?$/g,o=0;o=2})).sort((function(e,a){return e[1]>a[1]?-1:e[1]1})),i=(a.getAttribute("data-ea-campaign-types")||"").split("|").filter((function(e){return e.length>1})),n="true"===a.getAttribute("data-ea-manual"),l=a.getAttribute("data-ea-style"),d=a.getAttribute("data-ea-force-ad"),c=a.getAttribute("data-ea-force-campaign");if("image"!==o&&"text"!==o||(o+="-v1"),(a.className||"").split(" ").indexOf("loaded")>=0)return y.warn("EthicalAd already loaded."),null;var s=a.getAttribute("data-ea-placement-bottom");return s&&a.style.setProperty("bottom",s),new e(t,o,a,{keywords:r,style:l,campaign_types:i,load_manually:n,force_ad:d,force_campaign:c})}}])}();function x(){return!!(Object.entries&&window.URL&&window.URLSearchParams&&window.Promise)||(y.error("Browser does not meet ethical ad client dependencies. Not showing ads"),!1)}function w(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],a=document.querySelectorAll("[data-ea-publisher]"),t=Array.prototype.slice.call(a);return 0===t.length&&y.warn("No ad placements found."),Promise.all(t.map((function(a,t){var o=v.from_element(a);return o?(o.index=t,0===t&&o&&!e&&o.detectABP("https://media.ethicalads.io/abp/px.gif",(function(e){E=e,e&&y.debug("Acceptable Ads enabled. Thanks for allowing our non-tracking ads :)")})),!o||!e&&o.load_manually?null:o.load()):null})))}function k(){var e=document.querySelectorAll("[data-ea-publisher]");Array.prototype.slice.call(e).forEach((function(e){e.innerHTML="",e.classList.remove("loaded")}))}function _(){var e=document.querySelector("[data-ea-publisher]");if(e){var a=e.getAttribute("data-ea-verbosity");g.hasOwnProperty(a)&&(y.verbosity=g[a])}}var j,S,z,A=function(e){function a(){return p(this,a),n(this,a,arguments)}return function(e,a){if("function"!=typeof a&&null!==a)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(a&&a.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),a&&s(e,a)}(a,e),m(a)}(l(Error)),E=!1,O=null;if(window.ethicalads&&console.warn("Double-loading the EthicalAds client. Use reload() instead. https://ethical-ad-client.readthedocs.io/en/latest/#single-page-apps"),x()){_();var C=new Promise((function(e){if("interactive"===document.readyState||"complete"===document.readyState)return e();document.addEventListener("DOMContentLoaded",(function(){e()}),{capture:!0,once:!0,passive:!0})}));j=new Promise((function(e){C.then((function(){w().then((function(a){e(a.filter((function(e){return null!=e})))})).catch((function(a){e([]),a instanceof A?y.warn(a.message):y.error(a.message)}))}))})),S=function(){y.debug("Loading placements manually"),w(!0).catch((function(e){e instanceof A?y.warn(e.message):y.error(e.message)}))},z=function(){y.debug("Reloading ad placement"),O=null,k(),w().catch((function(e){e instanceof A?y.warn(e.message):y.error(e.message)}))}}},function(e,a,t){var o=t(3),r=t(4);"string"==typeof(r=r.__esModule?r.default:r)&&(r=[[e.i,r,""]]);var i={insert:"head",singleton:!1};o(r,i);e.exports=r.locals||{}},function(e,a,t){"use strict";var o,r=function(){return void 0===o&&(o=Boolean(window&&document&&document.all&&!window.atob)),o},i=function(){var e={};return function(a){if(void 0===e[a]){var t=document.querySelector(a);if(window.HTMLIFrameElement&&t instanceof window.HTMLIFrameElement)try{t=t.contentDocument.head}catch(e){t=null}e[a]=t}return e[a]}}(),n=[];function l(e){for(var a=-1,t=0;ta>img,[data-ea-publisher]:not([data-ea-type]).loaded .ea-content>a>img,.ea-type-image .ea-content>a>img{width:var(--ea-image-width);height:auto;display:inline-block}[data-ea-type=image].loaded .ea-content>.ea-text,[data-ea-publisher]:not([data-ea-type]).loaded .ea-content>.ea-text,.ea-type-image .ea-content>.ea-text{margin-top:1em;font-size:1em;text-align:center}[data-ea-type=image].loaded .ea-callout,[data-ea-publisher]:not([data-ea-type]).loaded .ea-callout,.ea-type-image .ea-callout{max-width:var(--ea-image-placement-width);margin:0em 1em 1em 1em;padding-left:1em;padding-right:1em;font-style:italic;text-align:right}[data-ea-type=image].loaded.horizontal .ea-content,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-content,.ea-type-image.horizontal .ea-content{max-width:var(--ea-image-placement-width-horizontal)}[data-ea-type=image].loaded.horizontal .ea-content>a>img,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-content>a>img,.ea-type-image.horizontal .ea-content>a>img{float:left;margin-right:1em}[data-ea-type=image].loaded.horizontal .ea-content .ea-text,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-content .ea-text,.ea-type-image.horizontal .ea-content .ea-text{margin-top:0em;text-align:left;overflow:auto}[data-ea-type=image].loaded.horizontal .ea-callout,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-callout,.ea-type-image.horizontal .ea-callout{max-width:var(--ea-image-placement-width-horizontal);text-align:right}[data-ea-type=text].loaded,.ea-type-text{font-size:var(--ea-font-size)}[data-ea-type=text].loaded .ea-content,.ea-type-text .ea-content{text-align:left}[data-ea-type=text].loaded .ea-callout,.ea-type-text .ea-callout{margin:.5em 1em 1em 1em;padding-left:1em;padding-right:1em;text-align:right;font-style:italic}[data-ea-type=text-only-large-v1].loaded,[data-ea-type=logo-large-v1].loaded,.ea-type-text-large{font-size:var(--ea-font-size);margin:1em 0}[data-ea-type=text-only-large-v1].loaded .ea-logo,[data-ea-type=logo-large-v1].loaded .ea-logo,.ea-type-text-large .ea-logo{float:right;margin-left:1em;margin-bottom:1em}[data-ea-type=text-only-large-v1].loaded img,[data-ea-type=logo-large-v1].loaded img,.ea-type-text-large img{width:var(--ea-image-logo-width);height:auto;filter:var(--ea-image-logo-filter)}[data-ea-type=text-only-large-v1].loaded .ea-headline,[data-ea-type=logo-large-v1].loaded .ea-headline,.ea-type-text-large .ea-headline{font-size:var(--ea-font-size-text-only-header);font-weight:bold;margin-bottom:.5em;text-align:left}[data-ea-type=text-only-large-v1].loaded .ea-body,[data-ea-type=logo-large-v1].loaded .ea-body,.ea-type-text-large .ea-body{margin-bottom:.5em;text-align:left}[data-ea-type=text-only-large-v1].loaded .ea-cta,[data-ea-type=logo-large-v1].loaded .ea-cta,.ea-type-text-large .ea-cta{border-radius:.5em;border:1px solid var(--ea-color-link);color:var(--ea-color-link);display:inline-block;font-weight:bold;padding:.25em .5em}[data-ea-type=text-only-large-v1].loaded .ea-cta::after,[data-ea-type=logo-large-v1].loaded .ea-cta::after,.ea-type-text-large .ea-cta::after{content:" ↗"}[data-ea-type=text-only-large-v1].loaded .ea-callout,[data-ea-type=logo-large-v1].loaded .ea-callout,.ea-type-text-large .ea-callout{margin:.5em 1em 1em 1em;padding-left:1em;padding-right:1em;text-align:right;font-style:italic}[data-ea-style=stickybox].loaded{position:fixed;bottom:20px;right:20px;z-index:100}[data-ea-style=stickybox].loaded .ea-type-image .ea-stickybox-hide{cursor:pointer;position:absolute;top:.75em;right:.75em;background-color:#fefefe;border:1px solid #088cdb;border-radius:50%;color:#088cdb;font-size:1em;text-align:center;height:1.5em;width:1.5em;line-height:1.4}[data-ea-style=stickybox].loaded .ea-type-text{display:none !important}@media(max-width: 1300px){[data-ea-style=stickybox].loaded{position:static;bottom:0;right:0;margin:auto;text-align:center}[data-ea-style=stickybox].loaded .ea-stickybox-hide{display:none}}@media(min-width: 1301px){[data-ea-style=stickybox].loaded .ea-type-image .ea-content{background:var(--ea-stylefixed-bgcolor)}}[data-ea-style=fixedfooter].loaded{position:fixed;bottom:0;left:0;z-index:200;width:100%;max-width:100%}[data-ea-style=fixedfooter].loaded .ea-type-text{width:100%;max-width:100%;display:flex;z-index:200;background:var(--ea-stylefixed-bgcolor)}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-content{border:0px;border-radius:3px;box-shadow:none}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-content{background-color:inherit;max-width:100%;margin:0;padding:1em;flex:auto}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-callout{max-width:100%;margin:0;padding:1em;flex:initial}@media(max-width: 576px){[data-ea-style=fixedfooter].loaded .ea-type-text .ea-callout{display:none}}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-fixedfooter-hide{cursor:pointer;color:var(--ea-color-link);padding:1em;flex:initial;margin:auto 0}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-fixedfooter-hide span{padding:.25em;font-size:.8em;font-weight:bold;border:.15em solid var(--ea-color-link);border-radius:.5em;white-space:nowrap}[data-ea-style=fixedfooter].loaded .ea-type-image{display:none !important}[data-ea-style=fixedheader]{height:var(--ea-fixedheader-height);width:100%;max-width:100%;background:var(--ea-stylefixed-bgcolor);border-bottom:1px solid var(--ea-background-color)}@media(max-width: 768px){[data-ea-style=fixedheader]{display:none !important}}[data-ea-style=fixedheader].loaded .ea-type-image,[data-ea-style=fixedheader].loaded .ea-type-text{width:var(--ea-container-xl);margin:0 auto;display:flex}@media(max-width: 992px){[data-ea-style=fixedheader].loaded .ea-type-image,[data-ea-style=fixedheader].loaded .ea-type-text{width:var(--ea-container-md)}}@media(max-width: 1200px){[data-ea-style=fixedheader].loaded .ea-type-image,[data-ea-style=fixedheader].loaded .ea-type-text{width:var(--ea-container-lg)}}[data-ea-style=fixedheader].loaded .ea-type-image .ea-content,[data-ea-style=fixedheader].loaded .ea-type-text .ea-content{border:0px;border-radius:3px;box-shadow:none}[data-ea-style=fixedheader].loaded .ea-type-image .ea-content,[data-ea-style=fixedheader].loaded .ea-type-text .ea-content{background-color:inherit;max-width:100%;margin:0;padding:0;flex:auto;display:flex}[data-ea-style=fixedheader].loaded .ea-type-image .ea-content .ea-text,[data-ea-style=fixedheader].loaded .ea-type-text .ea-content .ea-text{margin-top:0;padding:1em;flex:auto;text-align:left}[data-ea-style=fixedheader].loaded .ea-type-image .ea-callout,[data-ea-style=fixedheader].loaded .ea-type-text .ea-callout{max-width:100%;margin:0;padding:1em;flex:initial}@media(max-width: 576px){[data-ea-style=fixedheader].loaded .ea-type-image .ea-callout,[data-ea-style=fixedheader].loaded .ea-type-text .ea-callout{display:none}}[data-ea-style=fixedheader].loaded .ea-type-image img{width:var(--ea-image-width-xs) !important;margin:.6em}[data-ea-style=fixedheader].loaded .ea-type-image .ea-domain{display:none}',""]),e.exports=a},function(e,a,t){"use strict";e.exports=function(e){var a=[];return a.toString=function(){return this.map((function(a){var t=function(e,a){var t=e[1]||"",o=e[3];if(!o)return t;if(a&&"function"==typeof btoa){var r=(n=o,l=btoa(unescape(encodeURIComponent(JSON.stringify(n)))),d="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(l),"/*# ".concat(d," */")),i=o.sources.map((function(e){return"/*# sourceURL=".concat(o.sourceRoot||"").concat(e," */")}));return[t].concat(i).concat([r]).join("\n")}var n,l,d;return[t].join("\n")}(a,e);return a[2]?"@media ".concat(a[2]," {").concat(t,"}"):t})).join("")},a.i=function(e,t,o){"string"==typeof e&&(e=[[null,e,""]]);var r={};if(o)for(var i=0;i=0&&a.left<=i()},e.inY=function(e,t){var a=c(e,t);return!!a&&a.bottom>=0&&a.top<=l()},e.inViewport=function(e,t){var a=c(e,t);return!!a&&a.bottom>=0&&a.right>=0&&a.top<=l()&&a.left<=i()},e},e.exports?e.exports=r():o.verge=r()},function(e,t,a){"use strict";a.r(t),a.d(t,"Placement",(function(){return x})),a.d(t,"check_dependencies",(function(){return k})),a.d(t,"load_placements",(function(){return _})),a.d(t,"unload_placements",(function(){return j})),a.d(t,"set_verbosity",(function(){return S})),a.d(t,"wait",(function(){return A})),a.d(t,"load",(function(){return E})),a.d(t,"reload",(function(){return z})),a.d(t,"uplifted",(function(){return O})),a.d(t,"detectedKeywords",(function(){return C}));var o=a(0),r=a.n(o);a(2);function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function i(e,t,a){return t=u(t),function(e,t){if(t&&("object"==n(t)||"function"==typeof t))return t;if(void 0!==t)throw new TypeError("Derived constructors may only return object or undefined");return function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e)}(e,c()?Reflect.construct(t,a||[],u(e).constructor):t.apply(e,a))}function l(e){var t="function"==typeof Map?new Map:void 0;return(l=function(e){if(null===e||!function(e){try{return-1!==Function.toString.call(e).indexOf("[native code]")}catch(t){return"function"==typeof e}}(e))return e;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,a)}function a(){return d(e,arguments,u(this).constructor)}return a.prototype=Object.create(e.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}}),s(a,e)})(e)}function d(e,t,a){if(c())return Reflect.construct.apply(null,arguments);var o=[null];o.push.apply(o,t);var r=new(e.bind.apply(e,o));return a&&s(r,a.prototype),r}function c(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){})))}catch(e){}return(c=function(){return!!e})()}function s(e,t){return(s=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function p(e){return function(e){if(Array.isArray(e))return f(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(e){if("string"==typeof e)return f(e,t);var a={}.toString.call(e).slice(8,-1);return"Object"===a&&e.constructor&&(a=e.constructor.name),"Map"===a||"Set"===a?Array.from(e):"Arguments"===a||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(a)?f(e,t):void 0}}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function f(e,t){(null==t||t>e.length)&&(t=e.length);for(var a=0,o=Array(t);a=v.verbose){for(var t,a=arguments.length,o=new Array(a>1?a-1:0),r=1;r=v.verbose){for(var t,a=arguments.length,o=new Array(a>1?a-1:0),r=1;r=v.normal){for(var t,a=arguments.length,o=new Array(a>1?a-1:0),r=1;r=v.quiet){for(var t,a=arguments.length,o=new Array(a>1?a-1:0),r=1;r0&&void 0!==arguments[0]?arguments[0]:null;return this.keywords=this.keywords.concat(this.detectKeywords()),(t=t||this.fetch()).then((function(t){if(void 0===t)throw new M("Ad decision request blocked or invalid.");if(!t)throw new M("No ads to show.");var a=e.target.className||"";for(a+=" loaded",e.target.className=a.trim();e.target.firstChild;)e.target.removeChild(e.target.firstChild);return e.applyStyles(t),e.target.appendChild(t),e})).then((function(t){var a=setInterval((function(e){if(t.inViewport(e)){var o=document.createElement("img");o.src=t.response.view_url,O&&(o.src+="?uplift=true"),o.className="ea-pixel",e.appendChild(o),clearInterval(a)}}),100,t.target);return t.view_time_counter=setInterval((function(e){!1===t.tab_hidden&&t.inViewport(e)&&(t.view_time+=1,t.view_time>=300&&clearInterval(t.view_time_counter))}),1e3,t.target),t.hashchange_listener=function(){t.canRotate()&&(t.sendViewTime(),t.rotate())},window.addEventListener("hashchange",t.hashchange_listener),t.visibilitychange_listener=function(){"hidden"!==document.visibilityState&&"unloaded"!==document.visibilityState||(t.tab_hidden=!0,t.sendViewTime()),!0===t.tab_hidden&&"visible"===document.visibilityState&&(t.tab_hidden=!1,t.canRotate()&&(t.sendViewTime(),setTimeout((function(){t.rotate()}),3e3)))},document.addEventListener("visibilitychange",t.visibilitychange_listener),e}))}},{key:"clearListeners",value:function(){this.view_time_counter&&clearInterval(this.view_time_counter),this.hashchange_listener&&window.removeEventListener("hashchange",this.hashchange_listener),this.visibilitychange_listener&&document.removeEventListener("visibilitychange",this.visibilitychange_listener)}},{key:"canRotate",value:function(){return!(!this.inViewport(this.target)||this.view_time<45||this.rotations>=3)}},{key:"rotate",value:function(){if(this.canRotate())return b.debug("Rotating ad"),this.clearListeners(),this.view_time=0,this.view_time_sent=!1,this.response=null,this.tab_hidden=!1,this.rotations+=1,this.load()}},{key:"inViewport",value:function(e){return!!(this.response&&this.response.view_url&&r.a.inViewport(e,-3)&&"visible"===document.visibilityState)}},{key:"fetch",value:function(){var e=this,t="ad_"+Date.now()+"_"+Math.floor(1e6*Math.random()),a=this.div_id,o={publisher:this.publisher,ad_types:this.ad_type,div_ids:a,callback:t,keywords:this.keywords.join("|"),campaign_types:this.campaign_types.join("|"),format:"jsonp",client_version:"2.4.0-alpha",placement_index:this.index,url:(window.location.origin+window.location.pathname).slice(0,256)};this.force_ad&&(o.force_ad=this.force_ad),this.force_campaign&&(o.force_campaign=this.force_campaign),this.rotations>1&&(o.rotations=this.rotations);var r=new URLSearchParams(o),n=new URL("https://server.ethicalads.io/api/v1/decision/?"+r.toString());return new Promise((function(a,o){window[t]=function(t){if(t&&t.html&&t.view_url){e.response=t;var o=document.createElement("div");return o.innerHTML=t.html,a(o.firstChild)}return a(null)};var r=document.createElement("script");r.src=n,r.type="text/javascript",r.async=!0,r.addEventListener("error",(function(e){return a()})),document.getElementsByTagName("head")[0].appendChild(r)}))}},{key:"sendViewTime",value:function(){if(!(this.view_time<=0||this.view_time_sent)&&this.response&&this.response.view_time_url){var e=document.createElement("img");e.src=this.response.view_time_url+"?view_time="+this.view_time,e.className="ea-pixel",this.target.appendChild(e),this.view_time_sent=!0}}},{key:"detectABP",value:function(e,t){var a=!1,o=2,r=!1,n=!1;if("function"==typeof t){e+="?ch=*&rn=*";var i=11*Math.random(),l=new Image;l.onload=c,l.onerror=function(){r=!0,c()},l.src=e.replace(/\*/,1).replace(/\*/,i);var d=new Image;d.onload=c,d.onerror=function(){n=!0,c()},d.src=e.replace(/\*/,2).replace(/\*/,i),function e(t,r){0==o||r>1e3?t(0==o&&a):setTimeout((function(){e(t,2*r)}),2*r)}(t,250)}function c(){--o||(a=!r&&n)}}},{key:"detectKeywords",value:function(){if(C)return C;for(var e={},t=(document.querySelector("[role='main']")||document.querySelector("main")||document.querySelector("body")).textContent.split(/\s+/),a=/^[\('"]?(.*?)[,\.\?\!:;\)'"]?$/g,o=0;o=2})).sort((function(e,t){return e[1]>t[1]?-1:e[1]1})),i=(t.getAttribute("data-ea-campaign-types")||"").split("|").filter((function(e){return e.length>1})),l="true"===t.getAttribute("data-ea-manual"),d=t.getAttribute("data-ea-style"),c=t.getAttribute("data-ea-force-ad"),s=t.getAttribute("data-ea-force-campaign"),u=t.getAttribute("data-ea-priority");if(null!==u&&""!==u){var p=parseInt(u,10);!Number.isNaN(p)&&p>=1&&p<=10?r=p:b.warn("EthicalAd: Invalid numerical priority '%s' provided; ignoring.",u)}if("image"!==o&&"text"!==o||(o+="-v1"),(t.className||"").split(" ").indexOf("loaded")>=0)return b.warn("EthicalAd already loaded."),null;var f=t.getAttribute("data-ea-placement-bottom");return f&&t.style.setProperty("bottom",f),new e(a,o,t,{keywords:n,style:d,campaign_types:i,load_manually:l,force_ad:c,force_campaign:s,priority:r})}},{key:"loadGroup",value:function(e){var t,a;if(!e||!e.length)return Promise.resolve([]);var o=e[0].publisher;if(!e.every((function(e){return e.publisher===o})))throw new Error("Multiple ad placements with different publishers are not allowed.");var r="ad_"+Date.now()+"_"+Math.floor(1e9*Math.random()),n=[];e.forEach((function(e){Array.isArray(e.keywords)||(e.keywords=[]),0===e.keywords.length&&(e.keywords=e.keywords.concat(e.detectKeywords())),n=n.concat(e.keywords)})),n=p(new Set(n));var i={publisher:o,ad_types:e.map((function(e){return e.ad_type})).join("|"),div_ids:e.map((function(e){return e.div_id})).join("|"),priorities:e.map((function(e){return e.priority})).join("|"),callback:r,keywords:n.join("|"),campaign_types:p(new Set(e.reduce((function(e,t){return e.concat(t.campaign_types)}),[]))).join("|"),format:"jsonp",client_version:"2.4.0-alpha",url:(window.location.origin+window.location.pathname).slice(0,256)},l=null===(t=e.find((function(e){return e.force_ad})))||void 0===t?void 0:t.force_ad;l&&(i.force_ad=l);var d=null===(a=e.find((function(e){return e.force_campaign})))||void 0===a?void 0:a.force_campaign;d&&(i.force_campaign=d);var c=Math.max.apply(Math,p(e.map((function(e){return e.rotations}))));c>1&&(i.rotations=c);var s=new URLSearchParams(i),u=new URL("https://server.ethicalads.io/api/v1/decision/?"+s.toString()),f=new Promise((function(e,t){window[r]=function(t){e(t)};var a=document.createElement("script");a.src=u,a.type="text/javascript",a.async=!0,a.addEventListener("error",(function(t){e()})),document.getElementsByTagName("head")[0].appendChild(a)}));return Promise.all(e.map((function(e,t){var a=f.then((function(a){var o=!1;if(a&&a.html&&a.view_url&&(o=a.div_id?a.div_id===e.div_id:0===t),o){e.response=a;var r=document.createElement("div");return r.innerHTML=a.html,r.firstChild}return null}));return e.load(a).catch((function(e){return e instanceof M?b.warn(e.message):b.error(e.message),null}))})))}}])}();function k(){return!!(Object.entries&&window.URL&&window.URLSearchParams&&window.Promise)||(b.error("Browser does not meet ethical ad client dependencies. Not showing ads"),!1)}function _(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=document.querySelectorAll("[data-ea-publisher]"),a=Array.prototype.slice.call(t),o=new Set;if(a.forEach((function(e){var t=e.getAttribute("data-ea-publisher");t&&o.add(t)})),o.size>1)throw new Error("Multiple ad placements with different publishers are not allowed.");0===a.length&&b.warn("No ad placements found.");var r=a.map((function(e,t){var a=x.from_element(e);return a?(a.index=t,a):null})),n=r.find((function(e){return null!==e}));n&&!e&&n.detectABP("https://media.ethicalads.io/abp/px.gif",(function(e){O=e,e&&b.debug("Acceptable Ads enabled. Thanks for allowing our non-tracking ads :)")}));var i=r.some((function(e){return e&&null!==e.priority}));if(i){var l=[];return r.forEach((function(t){!t||!e&&t.load_manually||(null===t.priority&&(t.priority=1),l.push(t))})),l.length>0?x.loadGroup(l):Promise.resolve([])}return Promise.all(r.map((function(t){return!t||!e&&t.load_manually?null:t.load().catch((function(e){return e instanceof M?b.warn(e.message):b.error(e.message),null}))})))}function j(){var e=document.querySelectorAll("[data-ea-publisher]");Array.prototype.slice.call(e).forEach((function(e){e.innerHTML="",e.classList.remove("loaded")}))}function S(){var e=document.querySelector("[data-ea-publisher]");if(e){var t=e.getAttribute("data-ea-verbosity");v.hasOwnProperty(t)&&(b.verbosity=v[t])}}var A,E,z,M=function(e){function t(){return m(this,t),i(this,t,arguments)}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&s(e,t)}(t,e),g(t)}(l(Error)),O=!1,C=null;if(window.ethicalads&&console.warn("Double-loading the EthicalAds client. Use reload() instead. https://ethical-ad-client.readthedocs.io/en/latest/#single-page-apps"),k()){S();var P=new Promise((function(e){if("interactive"===document.readyState||"complete"===document.readyState)return e();document.addEventListener("DOMContentLoaded",(function(){e()}),{capture:!0,once:!0,passive:!0})}));A=new Promise((function(e){P.then((function(){_().then((function(t){e(t.filter((function(e){return null!=e})))})).catch((function(t){e([]),t instanceof M?b.warn(t.message):b.error(t.message)}))}))})),E=function(){b.debug("Loading placements manually"),_(!0).catch((function(e){e instanceof M?b.warn(e.message):b.error(e.message)}))},z=function(){b.debug("Reloading ad placement"),C=null,j(),_().catch((function(e){e instanceof M?b.warn(e.message):b.error(e.message)}))}}},function(e,t,a){var o=a(3),r=a(4);"string"==typeof(r=r.__esModule?r.default:r)&&(r=[[e.i,r,""]]);var n={insert:"head",singleton:!1};o(r,n);e.exports=r.locals||{}},function(e,t,a){"use strict";var o,r=function(){return void 0===o&&(o=Boolean(window&&document&&document.all&&!window.atob)),o},n=function(){var e={};return function(t){if(void 0===e[t]){var a=document.querySelector(t);if(window.HTMLIFrameElement&&a instanceof window.HTMLIFrameElement)try{a=a.contentDocument.head}catch(e){a=null}e[t]=a}return e[t]}}(),i=[];function l(e){for(var t=-1,a=0;aa>img,[data-ea-publisher]:not([data-ea-type]).loaded .ea-content>a>img,.ea-type-image .ea-content>a>img{width:var(--ea-image-width);height:auto;display:inline-block}[data-ea-type=image].loaded .ea-content>.ea-text,[data-ea-publisher]:not([data-ea-type]).loaded .ea-content>.ea-text,.ea-type-image .ea-content>.ea-text{margin-top:1em;font-size:1em;text-align:center}[data-ea-type=image].loaded .ea-callout,[data-ea-publisher]:not([data-ea-type]).loaded .ea-callout,.ea-type-image .ea-callout{max-width:var(--ea-image-placement-width);margin:0em 1em 1em 1em;padding-left:1em;padding-right:1em;font-style:italic;text-align:right}[data-ea-type=image].loaded.horizontal .ea-content,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-content,.ea-type-image.horizontal .ea-content{max-width:var(--ea-image-placement-width-horizontal)}[data-ea-type=image].loaded.horizontal .ea-content>a>img,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-content>a>img,.ea-type-image.horizontal .ea-content>a>img{float:left;margin-right:1em}[data-ea-type=image].loaded.horizontal .ea-content .ea-text,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-content .ea-text,.ea-type-image.horizontal .ea-content .ea-text{margin-top:0em;text-align:left;overflow:auto}[data-ea-type=image].loaded.horizontal .ea-callout,[data-ea-publisher]:not([data-ea-type]).loaded.horizontal .ea-callout,.ea-type-image.horizontal .ea-callout{max-width:var(--ea-image-placement-width-horizontal);text-align:right}[data-ea-type=text].loaded,.ea-type-text{font-size:var(--ea-font-size)}[data-ea-type=text].loaded .ea-content,.ea-type-text .ea-content{text-align:left}[data-ea-type=text].loaded .ea-callout,.ea-type-text .ea-callout{margin:.5em 1em 1em 1em;padding-left:1em;padding-right:1em;text-align:right;font-style:italic}[data-ea-type=text-only-large-v1].loaded,[data-ea-type=logo-large-v1].loaded,.ea-type-text-large{font-size:var(--ea-font-size);margin:1em 0}[data-ea-type=text-only-large-v1].loaded .ea-logo,[data-ea-type=logo-large-v1].loaded .ea-logo,.ea-type-text-large .ea-logo{float:right;margin-left:1em;margin-bottom:1em}[data-ea-type=text-only-large-v1].loaded img,[data-ea-type=logo-large-v1].loaded img,.ea-type-text-large img{width:var(--ea-image-logo-width);height:auto;filter:var(--ea-image-logo-filter)}[data-ea-type=text-only-large-v1].loaded .ea-headline,[data-ea-type=logo-large-v1].loaded .ea-headline,.ea-type-text-large .ea-headline{font-size:var(--ea-font-size-text-only-header);font-weight:bold;margin-bottom:.5em;text-align:left}[data-ea-type=text-only-large-v1].loaded .ea-body,[data-ea-type=logo-large-v1].loaded .ea-body,.ea-type-text-large .ea-body{margin-bottom:.5em;text-align:left}[data-ea-type=text-only-large-v1].loaded .ea-cta,[data-ea-type=logo-large-v1].loaded .ea-cta,.ea-type-text-large .ea-cta{border-radius:.5em;border:1px solid var(--ea-color-link);color:var(--ea-color-link);display:inline-block;font-weight:bold;padding:.25em .5em}[data-ea-type=text-only-large-v1].loaded .ea-cta::after,[data-ea-type=logo-large-v1].loaded .ea-cta::after,.ea-type-text-large .ea-cta::after{content:" ↗"}[data-ea-type=text-only-large-v1].loaded .ea-callout,[data-ea-type=logo-large-v1].loaded .ea-callout,.ea-type-text-large .ea-callout{margin:.5em 1em 1em 1em;padding-left:1em;padding-right:1em;text-align:right;font-style:italic}[data-ea-style=stickybox].loaded{position:fixed;bottom:20px;right:20px;z-index:100}[data-ea-style=stickybox].loaded .ea-type-image .ea-stickybox-hide{cursor:pointer;position:absolute;top:.75em;right:.75em;background-color:#fefefe;border:1px solid #088cdb;border-radius:50%;color:#088cdb;font-size:1em;text-align:center;height:1.5em;width:1.5em;line-height:1.4}[data-ea-style=stickybox].loaded .ea-type-text{display:none !important}@media(max-width: 1300px){[data-ea-style=stickybox].loaded{position:static;bottom:0;right:0;margin:auto;text-align:center}[data-ea-style=stickybox].loaded .ea-stickybox-hide{display:none}}@media(min-width: 1301px){[data-ea-style=stickybox].loaded .ea-type-image .ea-content{background:var(--ea-stylefixed-bgcolor)}}[data-ea-style=fixedfooter].loaded{position:fixed;bottom:0;left:0;z-index:200;width:100%;max-width:100%}[data-ea-style=fixedfooter].loaded .ea-type-text{width:100%;max-width:100%;display:flex;z-index:200;background:var(--ea-stylefixed-bgcolor)}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-content{border:0px;border-radius:3px;box-shadow:none}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-content{background-color:inherit;max-width:100%;margin:0;padding:1em;flex:auto}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-callout{max-width:100%;margin:0;padding:1em;flex:initial}@media(max-width: 576px){[data-ea-style=fixedfooter].loaded .ea-type-text .ea-callout{display:none}}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-fixedfooter-hide{cursor:pointer;color:var(--ea-color-link);padding:1em;flex:initial;margin:auto 0}[data-ea-style=fixedfooter].loaded .ea-type-text .ea-fixedfooter-hide span{padding:.25em;font-size:.8em;font-weight:bold;border:.15em solid var(--ea-color-link);border-radius:.5em;white-space:nowrap}[data-ea-style=fixedfooter].loaded .ea-type-image{display:none !important}[data-ea-style=fixedheader]{height:var(--ea-fixedheader-height);width:100%;max-width:100%;background:var(--ea-stylefixed-bgcolor);border-bottom:1px solid var(--ea-background-color)}@media(max-width: 768px){[data-ea-style=fixedheader]{display:none !important}}[data-ea-style=fixedheader].loaded .ea-type-image,[data-ea-style=fixedheader].loaded .ea-type-text{width:var(--ea-container-xl);margin:0 auto;display:flex}@media(max-width: 992px){[data-ea-style=fixedheader].loaded .ea-type-image,[data-ea-style=fixedheader].loaded .ea-type-text{width:var(--ea-container-md)}}@media(max-width: 1200px){[data-ea-style=fixedheader].loaded .ea-type-image,[data-ea-style=fixedheader].loaded .ea-type-text{width:var(--ea-container-lg)}}[data-ea-style=fixedheader].loaded .ea-type-image .ea-content,[data-ea-style=fixedheader].loaded .ea-type-text .ea-content{border:0px;border-radius:3px;box-shadow:none}[data-ea-style=fixedheader].loaded .ea-type-image .ea-content,[data-ea-style=fixedheader].loaded .ea-type-text .ea-content{background-color:inherit;max-width:100%;margin:0;padding:0;flex:auto;display:flex}[data-ea-style=fixedheader].loaded .ea-type-image .ea-content .ea-text,[data-ea-style=fixedheader].loaded .ea-type-text .ea-content .ea-text{margin-top:0;padding:1em;flex:auto;text-align:left}[data-ea-style=fixedheader].loaded .ea-type-image .ea-callout,[data-ea-style=fixedheader].loaded .ea-type-text .ea-callout{max-width:100%;margin:0;padding:1em;flex:initial}@media(max-width: 576px){[data-ea-style=fixedheader].loaded .ea-type-image .ea-callout,[data-ea-style=fixedheader].loaded .ea-type-text .ea-callout{display:none}}[data-ea-style=fixedheader].loaded .ea-type-image img{width:var(--ea-image-width-xs) !important;margin:.6em}[data-ea-style=fixedheader].loaded .ea-type-image .ea-domain{display:none}',""]),e.exports=t},function(e,t,a){"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var a=function(e,t){var a=e[1]||"",o=e[3];if(!o)return a;if(t&&"function"==typeof btoa){var r=(i=o,l=btoa(unescape(encodeURIComponent(JSON.stringify(i)))),d="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(l),"/*# ".concat(d," */")),n=o.sources.map((function(e){return"/*# sourceURL=".concat(o.sourceRoot||"").concat(e," */")}));return[a].concat(n).concat([r]).join("\n")}var i,l,d;return[a].join("\n")}(t,e);return t[2]?"@media ".concat(t[2]," {").concat(a,"}"):a})).join("")},t.i=function(e,a,o){"string"==typeof e&&(e=[[null,e,""]]);var r={};if(o)for(var n=0;n`,
this will allow you to see reports for each ``id``.
+``data-ea-priority`` (optional)
+ A numerical priority for the placement in range [1, 10]. If multiple placements on a page define a priority,
+ the server will choose only one ad to return for the group based on the priority and available inventory.
+ Setting a priority when the page has a single placement will have no effect.
+
``data-ea-style`` (optional)
Use a custom :ref:`placement style `.
@@ -343,6 +348,20 @@ Large format placements can be defined using ``data-ea-type="logo-large-v1"``.
:ad_type: large
:classes: dark raised
+The large format placement can be used with a priority to prioritize this placement
+but fallback to a normal ad if no large format ad is available
+or if the server is prioritizing smaller format ads.
+Only one ad will be chosen to be displayed on the page.
+
+.. code:: html
+
+
+
+
+
+
+
+
.. _placement-styles:
diff --git a/index.html b/index.html
index 070cad7..1a7f882 100644
--- a/index.html
+++ b/index.html
@@ -1,6 +1,8 @@
-
+
+
+