From dad990fe598fd110e5d1a269896a371cec477b78 Mon Sep 17 00:00:00 2001 From: Nicole Karhoff <45244877+nkarhoff@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:07:48 -0400 Subject: [PATCH] TW29378047 Explo Eclipse Hero (#45) * Hero Paragraph Initial Setup * Set up vertical alignment * Styles for mobile * Adjust min-height on mobile * Switch to Video media * Adjust position for hero content if no overlay is set * Update template to include remote video * Fix field name in hero template * Add patch-package and patch youtube-background to use aria-checked * Remove underscore from hero.js file name and fix extra js folder in libraries file * Fix hero content positioning on mobile * Update patch to add aria-label to Play and Mute buttons --- assets/js/hero.js | 2 + assets/scss/_hero.scss | 125 ++++++++ package-lock.json | 17 +- package.json | 6 +- patches/youtube-background+1.1.8.patch | 302 ++++++++++++++++++ saplings_child.libraries.yml | 7 + ...d--paragraph--sa-hero-background.html.twig | 3 + ...ield--paragraph--sa-hero-content.html.twig | 3 + .../paragaphs/paragraph--sa-hero.html.twig | 137 ++++++++ 9 files changed, 599 insertions(+), 3 deletions(-) create mode 100644 assets/js/hero.js create mode 100644 assets/scss/_hero.scss create mode 100644 patches/youtube-background+1.1.8.patch create mode 100644 templates/overrides/fields/field--paragraph--sa-hero-background.html.twig create mode 100644 templates/overrides/fields/field--paragraph--sa-hero-content.html.twig create mode 100644 templates/overrides/paragaphs/paragraph--sa-hero.html.twig diff --git a/assets/js/hero.js b/assets/js/hero.js new file mode 100644 index 0000000..5b497a7 --- /dev/null +++ b/assets/js/hero.js @@ -0,0 +1,2 @@ +import 'youtube-background'; +new VideoBackgrounds('[data-vbg]'); diff --git a/assets/scss/_hero.scss b/assets/scss/_hero.scss new file mode 100644 index 0000000..12cf496 --- /dev/null +++ b/assets/scss/_hero.scss @@ -0,0 +1,125 @@ +.paragraph--type--sa-hero { + display: flex; + flex-direction: column; + // justify-content: center; + width: 100%; + overflow: hidden; + position: relative; + + @include media-breakpoint-up(lg) { + min-height: 55vh; + } + + + + #hero-video-control { + background-color: unset; + border: 0; + + &.play { + .bi-pause { + display: none; + } + } + + &.pause { + .bi-play { + display: none; + } + } + + .bi { + font-size: 1.5rem; + } + } + + .hero-image { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + aspect-ratio: 16 / 9; + + @include media-breakpoint-down(lg) { + position: relative !important; + iframe { + height: 100% !important; + width: 100% !important; + } + } + } + + &.video { + @include media-breakpoint-up(lg) { + aspect-ratio: 16 / 9; + } + + video { + height: 100%; + width: 100%; + } + } + + @include media-breakpoint-down(lg) { + .hero-content { + color: white; + + .bi { + color: white; + } + } + } + + @include media-breakpoint-up(lg) { + .hero-content { + position: relative; + } + + /* Overlay. */ + &[data-overlay] { + .hero-content { + padding: 20px; + color: white; + + .bi { + color: white; + } + + // Set all inner content to be higher z-index than before. + .text-wrapper, #hero-video-control { + position: relative; + z-index: 2; + } + + &:before { + background: #252525 none repeat scroll 0 0; + content: ""; + display: block; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + z-index: 0; + } + + &--overlay-invert { + &:before { + background: #fff none repeat scroll 0 0; + } + } + } + // Loops 1 - 9 and adds opacity $i x 0.1 + @for $i from 1 through 9 { + &[data-overlay="#{$i}"] { + .hero-content { + &:before { + opacity: calc($i * 0.1); + } + } + } + } + } + } +} diff --git a/package-lock.json b/package-lock.json index 7d15c1a..4b38542 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,11 +7,13 @@ "": { "name": "saplings_child", "version": "5.0.0", + "hasInstallScript": true, "dependencies": { "cssnano": "^6.0.1", "dotenv": "^16.0.3", "patch-package": "^8.0.0", - "postcss-inline-svg": "^5.0.0" + "postcss-inline-svg": "^5.0.0", + "youtube-background": "^1.1.8" }, "devDependencies": { "@babel/core": "^7.17.7", @@ -3288,6 +3290,11 @@ "node": ">= 6" } }, + "node_modules/book-of-spells": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/book-of-spells/-/book-of-spells-1.0.32.tgz", + "integrity": "sha512-9uf85aVE//Etv7UBaYRLx5PABH7DhSJeqZyMZWyW1NCEa9FzZgWELUsL/OLKQL2WkXMsaevpRZWDk9fQpLgVfA==" + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -12655,6 +12662,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/youtube-background": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/youtube-background/-/youtube-background-1.1.8.tgz", + "integrity": "sha512-M2w1qiWlsA3Q4KerfDE+dNWv4tf8BTjiqq0WqX1Mm1nXhLYk1oUqR7mEm16OOka4goAoz4oVXRafbDGYKFhyhg==", + "dependencies": { + "book-of-spells": "^1.0.18" + } } } } diff --git a/package.json b/package.json index 149f34b..7a87bc3 100644 --- a/package.json +++ b/package.json @@ -33,12 +33,14 @@ "gulp": "gulp", "watch": "gulp watch", "build": "gulp build", - "criticalcss": "node critical-css/clean.js && node critical-css/criticalcss.mjs" + "criticalcss": "node critical-css/clean.js && node critical-css/criticalcss.mjs", + "postinstall": "patch-package" }, "dependencies": { "cssnano": "^6.0.1", "dotenv": "^16.0.3", "patch-package": "^8.0.0", - "postcss-inline-svg": "^5.0.0" + "postcss-inline-svg": "^5.0.0", + "youtube-background": "^1.1.8" } } diff --git a/patches/youtube-background+1.1.8.patch b/patches/youtube-background+1.1.8.patch new file mode 100644 index 0000000..0732c25 --- /dev/null +++ b/patches/youtube-background+1.1.8.patch @@ -0,0 +1,302 @@ +diff --git a/node_modules/youtube-background/jquery.youtube-background.js b/node_modules/youtube-background/jquery.youtube-background.js +index 771bfb1..220e48e 100644 +--- a/node_modules/youtube-background/jquery.youtube-background.js ++++ b/node_modules/youtube-background/jquery.youtube-background.js +@@ -7,7 +7,7 @@ + buttonObj.element.classList.add(buttonObj.stateClassName); + buttonObj.element.firstChild.classList.remove(buttonObj.stateChildClassNames[0]); + buttonObj.element.firstChild.classList.add(buttonObj.stateChildClassNames[1]); +- buttonObj.element.setAttribute("aria-pressed", false); ++ buttonObj.element.setAttribute("aria-checked", false); + } + function buttonOff(buttonObj) { + if (!buttonObj) +@@ -15,7 +15,7 @@ + buttonObj.element.classList.remove(buttonObj.stateClassName); + buttonObj.element.firstChild.classList.add(buttonObj.stateChildClassNames[0]); + buttonObj.element.firstChild.classList.remove(buttonObj.stateChildClassNames[1]); +- buttonObj.element.setAttribute("aria-pressed", true); ++ buttonObj.element.setAttribute("aria-checked", true); + } + function generateActionButton(obj, props) { + const btn = document.createElement("button"); +@@ -23,7 +23,8 @@ + btn.innerHTML = props.innerHtml; + btn.setAttribute("role", "switch"); + btn.firstChild.classList.add(props.stateChildClassNames[0]); +- btn.setAttribute("aria-pressed", !props.initialState); ++ btn.setAttribute("aria-checked", !props.initialState); ++ btn.setAttribute("aria-label", props.ariaLabel); + props.element = btn; + if (obj.params[props.condition_parameter] === props.initialState) { + buttonOn(props); +@@ -267,7 +268,8 @@ + stateClassName: "paused", + condition_parameter: "paused", + stateChildClassNames: ["fa-pause-circle", "fa-play-circle"], +- actions: ["play", "pause"] ++ actions: ["play", "pause"], ++ ariaLabel: "Toggle Play/Pause" + }); + } + if (this.params["mute-button"]) { +@@ -279,7 +281,8 @@ + stateClassName: "muted", + condition_parameter: "muted", + stateChildClassNames: ["fa-volume-up", "fa-volume-mute"], +- actions: ["unmute", "mute"] ++ actions: ["unmute", "mute"], ++ ariaLabel: "Toggle Mute/Unmute" + }); + } + } +diff --git a/node_modules/youtube-background/jquery.youtube-background.js.map b/node_modules/youtube-background/jquery.youtube-background.js.map +index 278c2e6..cfa39b3 100644 +--- a/node_modules/youtube-background/jquery.youtube-background.js.map ++++ b/node_modules/youtube-background/jquery.youtube-background.js.map +@@ -1,7 +1,7 @@ + { + "version": 3, + "sources": ["src/lib/buttons.js", "node_modules/book-of-spells/src/helpers.mjs", "node_modules/book-of-spells/src/regex.mjs", "node_modules/book-of-spells/src/parsers.mjs", "node_modules/book-of-spells/src/dom.mjs", "node_modules/book-of-spells/src/browser.mjs", "src/lib/super-video-background.js", "src/lib/youtube-background.js", "src/lib/vimeo-background.js", "src/lib/video-background.js", "src/video-backgrounds.js", "src/main.js"], +- "sourcesContent": ["\nfunction buttonOn(buttonObj) {\n if (!buttonObj) return;\n buttonObj.element.classList.add(buttonObj.stateClassName);\n buttonObj.element.firstChild.classList.remove(buttonObj.stateChildClassNames[0]);\n buttonObj.element.firstChild.classList.add(buttonObj.stateChildClassNames[1]);\n buttonObj.element.setAttribute('aria-pressed', false);\n}\n\nfunction buttonOff(buttonObj) {\n if (!buttonObj) return;\n buttonObj.element.classList.remove(buttonObj.stateClassName);\n buttonObj.element.firstChild.classList.add(buttonObj.stateChildClassNames[0]);\n buttonObj.element.firstChild.classList.remove(buttonObj.stateChildClassNames[1]);\n buttonObj.element.setAttribute('aria-pressed', true);\n}\n\nexport function generateActionButton(obj, props) {\n const btn = document.createElement('button');\n btn.className = props.className;\n btn.innerHTML = props.innerHtml;\n btn.setAttribute('role', 'switch');\n btn.firstChild.classList.add(props.stateChildClassNames[0]);\n btn.setAttribute('aria-pressed', !props.initialState);\n props.element = btn;\n\n if (obj.params[props.condition_parameter] === props.initialState) {\n buttonOn(props);\n }\n\n btn.addEventListener('click', function(e) {\n if (this.classList.contains(props.stateClassName)) {\n buttonOff(props);\n obj[props.actions[0]]();\n } else {\n buttonOn(props);\n obj[props.actions[1]]();\n }\n });\n\n obj.buttons[props.name] = {\n element: btn,\n button_properties: props\n };\n\n obj.controls_element.appendChild(btn);\n};\n", "/**\n * Shallow merges two objects together. Used to pass simple options to functions.\n * \n * @param {object} target The target object to merge into\n * @param {object} source The source object to merge from\n * @returns object The merged object, in this case the target object with the source object's properties merged into it\n * @example\n * const target = { foo: 'bar' }\n * const source = { bar: 'baz' }\n * shallowMerge(target, source) // { foo: 'bar', bar: 'baz' }\n */\nexport function shallowMerge(target, source) {\n for (const key in source) {\n target[key] = source[key]\n }\n\n return target\n}\n\n/**\n * Deep merge function that's mindful of arrays and objects\n * \n * @param {object} target The target object to merge into\n * @param {object} source The source object to merge from\n * @returns object The merged object, in this case the target object with the source object's properties merged into it\n * @example\n * const target = { foo: 'bar' }\n * const source = { bar: 'baz' }\n * deepMerge(target, source) // { foo: 'bar', bar: 'baz' }\n */\nexport function deepMerge(target, source) {\n if (isObject(source) && isObject(target)) {\n for (const key in source) {\n target[key] = deepMerge(target[key], source[key])\n }\n } else if (isArray(source) && isArray(target)) {\n for (let i = 0; i < source.length; i++) {\n target[i] = deepMerge(target[i], source[i])\n }\n } else {\n target = source\n }\n return target\n}\n\n/**\n * Deep clone function that's mindful of nested arrays and objects\n * \n * @param {object} o The object to clone\n * @returns object The cloned object\n * @example\n * const obj = { foo: 'bar' }\n * const clone = clone(obj)\n * clone.foo = 'baz'\n * console.log(obj.foo) // 'bar'\n * console.log(clone.foo) // 'baz'\n * console.log(obj === clone) // false\n * console.log(JSON.stringify(obj) === JSON.stringify(clone)) // true\n * @todo Check if faster than assign. This function is pretty old...\n */ \nexport function clone(o) {\n let res = null\n if (isArray(o)) {\n res = []\n for (const i in o) {\n res[i] = clone(o[i])\n }\n } else if (isObject(o)) {\n res = {}\n for (const i in o) {\n res[i] = clone(o[i])\n }\n } else {\n res = o\n }\n return res\n}\n\n/**\n * Check if an object is empty\n * \n * @param {object} o The object to check\n * @returns boolean True if the object is empty, false otherwise\n * @example\n * isEmptyObject({}) // => true\n * isEmptyObject({ foo: 'bar' }) // => false\n */\nexport function isEmptyObject(o) {\n for (const i in o) {\n return false\n }\n return true\n}\n\n/**\n * Check if an array is empty, substitute for Array.length === 0\n * \n * @param {array} o The array to check\n * @returns boolean True if the array is empty, false otherwise\n * @example\n * isEmptyArray([]) // => true\n * isEmptyArray([1, 2, 3]) // => false\n */\nexport function isEmptyArray(o) {\n return o.length === 0\n}\n\n/**\n * Check if a variable is empty\n * \n * @param {any} o The variable to check\n * @returns boolean True if the variable is empty, false otherwise\n * @example\n * isEmpty({}) // => true\n * isEmpty([]) // => true\n * isEmpty('') // => true\n * isEmpty(null) // => false\n * isEmpty(undefined) // => false\n * isEmpty(0) // => false\n */\nexport function isEmpty(o) {\n if (isObject(o)) {\n return isEmptyObject(o)\n } else if (isArray(o)) {\n return isEmptyArray(o)\n } else if (isString(o)) {\n return o === ''\n }\n return false\n}\n\n/**\n * Try to convert a string to a boolean\n * \n * @param {string} str The string to convert\n * @returns boolean The converted boolean or undefined if conversion failed\n * @example\n * stringToBoolean('true') // => true\n * stringToBoolean('false') // => false\n * stringToBoolean('foo') // => null\n */\nexport function stringToBoolean(str) {\n if (/^\\s*(true|false)\\s*$/i.test(str)) return str === 'true'\n}\n\n/**\n * Try to convert a string to a number\n * \n * @param {string} str The string to convert\n * @returns number The converted number or undefined if conversion failed\n * @example\n * stringToNumber('1') // => 1\n * stringToNumber('1.5') // => 1.5\n * stringToNumber('foo') // => null\n * stringToNumber('1foo') // => null\n */\nexport function stringToNumber(str) {\n if (/^\\s*\\d+\\s*$/.test(str)) return parseInt(str)\n if (/^\\s*[\\d.]+\\s*$/.test(str)) return parseFloat(str)\n}\n\n/**\n * Try to convert a string to an array\n * \n * @param {string} str The string to convert\n * @returns array The converted array or undefined if conversion failed\n * @example\n * stringToArray('[1, 2, 3]') // => [1, 2, 3]\n * stringToArray('foo') // => null\n * stringToArray('1') // => null\n * stringToArray('{\"foo\": \"bar\"}') // => null\n */\nexport function stringToArray(str) {\n if (!/^\\s*\\[.*\\]\\s*$/.test(str)) return\n try {\n return JSON.parse(str)\n } catch (e) {}\n}\n\n/**\n * Try to convert a string to an object\n * \n * @param {string} str The string to convert\n * @returns object The converted object or undefined if conversion failed\n * @example\n * stringToObject('{ \"foo\": \"bar\" }') // => { foo: 'bar' }\n * stringToObject('foo') // => null\n * stringToObject('1') // => null\n * stringToObject('[1, 2, 3]') // => null\n */\nexport function stringToObject(str) {\n if (!/^\\s*\\{.*\\}\\s*$/.test(str)) return\n try {\n return JSON.parse(str)\n } catch (e) {}\n}\n\n/**\n * Try to convert a string to a regex\n * \n * @param {string} str The string to convert\n * @returns regex The converted regex or undefined if conversion failed\n * @example\n * stringToRegex('/foo/i') // => /foo/i\n * stringToRegex('foo') // => null\n * stringToRegex('1') // => null\n */\nexport function stringToRegex(str) {\n if (!/^\\s*\\/.*\\/g?i?\\s*$/.test(str)) return\n try {\n return new RegExp(str)\n } catch (e) {}\n}\n\n/**\n * Try to convert a string to a primitive\n * \n * @param {string} str The string to convert\n * @returns {null|boolean|int|float|string} The converted primitive or input string if conversion failed\n * @example\n * stringToPrimitive('null') // => null\n * stringToPrimitive('true') // => true\n * stringToPrimitive('false') // => false\n * stringToPrimitive('1') // => 1\n * stringToPrimitive('1.5') // => 1.5\n * stringToPrimitive('foo') // => 'foo'\n * stringToPrimitive('1foo') // => '1foo'\n */\nexport function stringToPrimitive(str) {\n if (/^\\s*null\\s*$/.test(str)) return null\n const bool = stringToBoolean(str)\n if (bool !== undefined) return bool\n return stringToNumber(str) || str\n}\n\n/**\n * Try to convert a string to a data type\n * \n * @param {string} str The string to convert\n * @returns any The converted data type or input string if conversion failed\n * @example\n * stringToData('null') // => null\n * stringToData('true') // => true\n * stringToData('false') // => false\n * stringToData('1') // => 1\n * stringToData('1.5') // => 1.5\n * stringToData('foo') // => 'foo'\n * stringToData('1foo') // => '1foo'\n * stringToData('[1, 2, 3]') // => [1, 2, 3]\n * stringToData('{ \"foo\": \"bar\" }') // => { foo: 'bar' }\n * stringToData('/foo/i') // => /foo/i\n */\nexport function stringToType(str) {\n if (/^\\s*null\\s*$/.test(str)) return null\n const bool = stringToBoolean(str)\n if (bool !== undefined) return bool\n return stringToNumber(str) || stringToArray(str) || stringToObject(str) || stringToRegex(str) || str\n}\n\n/**\n * If provided variable is an object\n * \n * @param {any} o \n * @returns boolean\n * @example\n * isObject({}) // => true\n * isObject([]) // => false\n * isObject(null) // => false\n */\nexport function isObject(o) {\n return typeof o === 'object' && !Array.isArray(o) && o !== null\n}\n\n/**\n * If provided variable is an array. Just a wrapper for Array.isArray\n * \n * @param {any} o\n * @returns boolean\n * @example\n * isArray([]) // => true\n * isArray({}) // => false\n */\nexport function isArray(o) {\n return Array.isArray(o)\n}\n\n/**\n * If provided variable is a string. Just a wrapper for typeof === 'string'\n * \n * @param {any} o\n * @returns boolean\n * @example\n * isString('foo') // => true\n * isString({}) // => false\n */\nexport function isString(o) {\n return typeof o === 'string'\n}\n\n/**\n * If provided variable is a function, substitute for typeof === 'function'\n * \n * @param {any} o\n * @returns boolean\n * @example\n * isFunction(function() {}) // => true\n * isFunction({}) // => false\n */\nexport function isFunction(o) {\n return typeof o === 'function'\n}\n\n/**\n * If object property is a function\n * \n * @param {object} obj\n * @param {string} propertyName\n * @returns boolean\n * @example\n * const obj = { foo: 'bar', baz: function() {} }\n * propertyIsFunction(obj, 'foo') // => false\n * propertyIsFunction(obj, 'baz') // => true\n */\nexport function propertyIsFunction(obj, propertyName) {\n return obj.hasOwnProperty(propertyName) && isFunction(obj[propertyName])\n}\n\n/**\n * If object property is a string\n * \n * @param {object} obj\n * @param {string} propertyName\n * @returns boolean\n * @example\n * const obj = { foo: 'bar', baz: function() {} }\n * propertyIsString(obj, 'foo') // => true\n * propertyIsString(obj, 'baz') // => false\n */\nexport function propertyIsString(obj, propertyName) {\n return obj.hasOwnProperty(propertyName) && isString(obj[propertyName])\n}\n\n/**\n * Transforms a dash separated string to camelCase\n *\n * @param {string} str\n * @returns boolean\n * @example\n * transformDashToCamelCase('foo-bar') // => 'fooBar'\n * transformDashToCamelCase('foo-bar-baz') // => 'fooBarBaz'\n * transformDashToCamelCase('foo') // => 'foo'\n * transformDashToCamelCase('fooBarBaz-qux') // => 'fooBarBazQux'\n */\nexport function transformDashToCamelCase(str) {\n return str.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase() });\n}\n\n/**\n * Transforms a camelCase string to dash separated string\n * \n * @param {string} str\n * @returns boolean\n * @example\n * transformCamelCaseToDash('fooBar') // => 'foo-bar'\n * transformCamelCaseToDash('fooBarBaz') // => 'foo-bar-baz'\n * transformCamelCaseToDash('foo') // => 'foo'\n * transformDashToCamelCase('fooBarBaz-qux') // => 'foo-bar-baz-qux'\n */\nexport function transformCamelCaseToDash(str) {\n return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()\n}\n\n/**\n * Maps an array of objects by a property name\n * \n * @param {array} arr\n * @param {string} propertyName\n * @returns object\n * @example\n * const arr = [{ foo: 'bar' }, { foo: 'baz' }]\n * mapByProperty(arr, 'foo') // => { bar: { foo: 'bar' }, baz: { foo: 'baz' } }\n */\nexport function mapByProperty(arr, propertyName) {\n const res = {}\n for (let i = 0; i < arr.length; i++) {\n res[arr[i][propertyName]] = arr[i]\n }\n return res\n}\n\n/**\n * Maps an array of objects by a property name to another property name\n * \n * @param {array} arr\n * @param {string} keyPropertyName\n * @param {string} valuePropertyName\n * @returns object\n * @example\n * const arr = [{ foo: 'bar', baz: 'qux' }, { foo: 'quux', baz: 'corge' }]\n * mapPropertyToProperty(arr, 'foo', 'baz') // => { bar: 'qux', quux: 'corge' }\n */\nexport function mapPropertyToProperty(arr, keyPropertyName, valuePropertyName) {\n const res = {}\n for (let i = 0; i < arr.length; i++) {\n res[arr[i][keyPropertyName]] = arr[i][valuePropertyName]\n }\n return res\n}\n\n/**\n * Remove accents from a string\n * \n * @param {string} inputString\n * @returns string\n * @example\n * removeAccents('\u00E1\u00E9\u00ED\u00F3\u00FA') // => 'aeiou'\n * removeAccents('\u00C1\u00C9\u00CD\u00D3\u00DA') // => 'AEIOU'\n * removeAccents('se\u00F1or') // => 'senor'\n * removeAccents('\u0152') // => 'OE'\n */\nexport function removeAccents(inputString) {\n return inputString.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').replace(/\\\u0153/g, \"oe\").replace(/\\\u00E6/g, \"ae\").normalize('NFC')\n}\n\n/**\n * Strip HTML tags from a string\n * \n * @param {string} inputString\n * @returns string\n * @example\n * stripHTMLTags('foo') // => 'foo'\n * stripHTMLTags('foo bar') // => 'foo bar'\n */\nexport function stripHTMLTags(inputString) {\n return inputString.replace(/<[^>]*>/g, '')\n}\n\n/**\n * Slugify a string, e.g. 'Foo Bar' => 'foo-bar'. Similar to WordPress' sanitize_title(). Will remove accents and HTML tags.\n * \n * @param {string} str \n * @returns string\n * @example\n * slugify('Foo Bar') // => 'foo-bar'\n * slugify('Foo Bar baz') // => 'foo-bar-baz'\n */\nexport function slugify(str) {\n str = str.trim().toLowerCase()\n str = removeAccents(str)\n str = stripHTMLTags(str)\n return str.replace(/\\s+|\\.+|\\/+|\\\\+|\u2014+|\u2013+/g, '-').replace(/[^\\w0-9\\-]+/g, '').replace(/-{2,}/g, '-').replace(/^-|-$/g, '')\n}\n\n/**\n * Check if object has multiple properties\n * \n * @param {object} obj\n * @param {string|array} properties\n * @returns boolean\n * @example\n * const obj = { foo: 'bar', baz: 'qux' }\n * hasOwnProperties(obj, ['foo', 'baz']) // => true\n * hasOwnProperties(obj, ['foo', 'baz', 'qux']) // => false\n */\nexport function hasOwnProperties(obj, properties) {\n if(!isArray(properties)) properties = [properties]\n for (let i = 0; i < properties.length; i++) {\n if (!obj.hasOwnProperty(properties[i])) return false\n }\n return true\n}\n\n/**\n * Finds the closest number to the set goal in an array to a given number\n * \n * @param {number} goal Number to search for\n * @param {array} arr Array of numbers to search in\n * @returns number\n * @example\n * closestNumber(10, [1, 2, 3, 4, 5, 6, 7, 8, 9]) // => 9\n * closestNumber(10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]) // => 9\n * closestNumber(10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 9.5]) // => 9.5\n * closestNumber(10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) // => 10\n */\nexport function closestNumber(goal, arr) {\n return arr.reduce(function(prev, curr) {\n return Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev\n })\n}\n\n/**\n * Truncate a string to a given number of words\n * \n * @param {string} str String to truncate\n * @param {number} numWords Number of words to truncate to\n * @param {string} ellipsis Ellipsis to append to the end of the string\n * @returns string\n * @example\n * truncateString('foo bar baz', 2) // => 'foo bar\u2026'\n * truncateString('foo bar baz', 2, '...') // => 'foo bar...'\n * truncateString('foo bar. baz', 2, '...') // => 'foo bar. ...'\n */\nexport function truncateString(str, numWords, ellipsis = '\u2026') {\n const words = str.trim().split(' ')\n if (words.length <= numWords) return str\n if (numWords <= 0) return ''\n if (/[.?!]$/.test(words[numWords - 1]) && ellipsis.trim() !== '') ellipsis = ` ${ellipsis}`\n return words.slice(0, numWords).join(' ') + ellipsis\n}\n\n/**\n * Generates a random integer between two values, inclusive of both\n * \n * @param {number} min Minimum value\n * @param {number} max Maximum value\n * @param {boolean} safe Defaults to false, if true will use a cryptographically secure random number generator\n * @returns number\n * @example\n * randomIntInclusive(1, 10) // => 1\n * randomIntInclusive(1, 10) // => 10\n * randomIntInclusive(1, 10) // => 5\n */\nexport function randomIntInclusive(min, max, safe = false) {\n min = Number(min)\n max = Number(max)\n if (isNaN(min) || isNaN(max)) throw new TypeError('Both min and max must be numbers')\n if (min > max) [min, max] = [max, min]\n if (min === max) return min\n min = Math.round(min)\n max = Math.round(max)\n const rand = safe ? random() : Math.random()\n return Math.floor(rand * (max - min + 1)) + min\n}\n\n/**\n * Gets fixed number of digits after the decimal point\n * \n * @param {number} number Number to fix\n * @param {number} digits Number of digits to fix to\n * @returns number\n * @example\n * fixed(1.234, 2) // => 1.23\n * fixed(1.235, 2) // => 1.24\n * fixed(1.234) // => 1\n * fixed(1.234, 0) // => 1\n * fixed(1.234, 5) // => 1.234\n * @note Gotta ask myself why I wrote this function in the first place... \uD83E\uDD14 It's just not useful in a lot of cases lol...\n */\nexport function fixed(number, digits) {\n if (!digits) return parseInt(number)\n return parseFloat(number.toFixed(digits))\n}\n\n/**\n * Calculates the percentage of a number in relation to another number\n * \n * @param {number} num Number to calculate percentage of\n * @param {number} total Total number\n * @returns number\n * @example\n * percentage(1, 10) // => 10\n * percentage(5, 10) // => 50\n * percentage(10, 10) // => 100\n * percentage(0, 10) // => 0\n * percentage(10, 2) // => 500\n */\nexport function percentage(num, total) {\n if (!num || !total || Number.isNaN(num) || Number.isNaN(total)) return 0\n return num / total * 100\n}\n\nexport function pickProperties(obj, props) {\n const res = {}\n if (!props) return res\n if (!isArray(props)) props = [props]\n for (let i = 0; i < props.length; i++) {\n if (obj.hasOwnProperty(props[i])) res[props[i]] = obj[props[i]]\n }\n return res\n}\n\nexport function rejectProperties(obj, props, clone = true) {\n if (clone) obj = { ...obj }\n if (!props) return obj\n if (!isArray(props)) props = [props]\n for (let i = 0; i < props.length; i++) {\n if (obj.hasOwnProperty(props[i])) delete obj[props[i]]\n }\n return obj\n}\n\nexport function pickArrayElements(arr, indexes) {\n if (!isArray(arr)) return\n if (!isArray(indexes)) indexes = [indexes]\n const res = []\n for (let i = 0; i < indexes.length; i++) {\n if (arr.hasOwnProperty(indexes[i])) res.push(arr[indexes[i]])\n }\n return res\n}\n\nexport function rejectArrayElements(arr, indexes, clone = true) {\n if (clone) arr = [...arr]\n if (!isArray(arr)) return\n if (!isArray(indexes)) indexes = [indexes]\n for (let i = indexes.length - 1; i >= 0; i--) {\n if (arr.hasOwnProperty(indexes[i])) arr.splice(indexes[i], 1)\n }\n return arr\n}\n\n/**\n * Pick properties from an object or elements from an array\n * \n * @param {array} obj Object or array to pick properties or elements from\n * @param {array | string | number} props Properties to remove, can be an array of strings or a single string or number\n * @returns object | array | undefined\n * @example\n * \n * pick({ foo: 'bar', bar: 'baz', baz: 'qux' }) // => {}\n * pick({}, []) // => {}\n * pick(null, 'foo') // => undefined\n * pick({ foo: 'bar', bar: 'baz', baz: 'qux' }, undefined) // => {}\n * pick({ foo: 'bar', bar: 'baz', baz: 'qux' }, 'foo') // => { foo: 'bar'}\n * pick({ foo: 'bar', bar: 'baz', baz: 'qux' }, ['foo', 'baz']) // => { foo: 'bar', baz: 'qux' }\n * \n * pick(['foo', 'bar', 'baz'], []) // => []\n * pick([], []) // => []\n * pick(null, 0) // => undefined\n * pick(['foo', 'bar', 'baz'], undefined) // => []\n * pick(['foo', 'bar', 'baz'], 0) // => ['foo']\n * pick(['foo', 'bar', 'baz'], [0, 2]) // => ['foo', 'baz']\n * pick(['foo', 'bar', 'baz'], [0, 2, 3]) // => ['foo', 'baz']\n */\nexport function pick(obj, props) {\n return isObject(obj) ? pickProperties(obj, props) : pickArrayElements(obj, props)\n}\n\n/**\n * Remove properties from an object or elements from an array\n * \n * @param {array} obj Object or array to remove properties or elements from\n * @param {array | string | number} props Properties to remove, can be an array of strings or a single string or number\n * @param {boolean} clone Defaults to true, will clone the object or array before removing properties or elements.\n * @returns object | array | undefined\n * @example\n * \n * reject({ foo: 'bar', bar: 'baz', baz: 'qux' }) // => {}\n * reject({}, []) // => {}\n * reject(null, 'foo') // => undefined\n * reject({ foo: 'bar', bar: 'baz', baz: 'qux' }, undefined) // => {}\n * reject({ foo: 'bar', bar: 'baz', baz: 'qux' }, 'foo') // => { bar: 'baz', baz: 'qux' }\n * reject({ foo: 'bar', bar: 'baz', baz: 'qux' }, ['foo', 'baz']) // => { bar: 'baz' }\n * \n * reject(['foo', 'bar', 'baz'], []) // => []\n * reject([], []) // => []\n * reject(null, 0) // => undefined\n * reject(['foo', 'bar', 'baz'], undefined) // => []\n * reject(['foo', 'bar', 'baz'], 0) // => ['bar', 'baz']\n * reject(['foo', 'bar', 'baz'], [0, 2]) // => ['bar']\n * reject(['foo', 'bar', 'baz'], [0, 2, 3]) // => ['bar']\n */\nexport function reject(obj, props, clone = true) {\n return isObject(obj) ? rejectProperties(obj, props, clone) : rejectArrayElements(obj, props, clone)\n}\n\n/**\n * Basic timestamp first UID generator that's good enough for most use cases but not for security purposes.\n * There's an extremely small chance of collision, so create a map object to check for collisions if you're worried about that.\n * \n * - `Date.now().toString(16)` is used for the timestamp, which is a base16 representation of the current timestamp in milliseconds.\n * - `random().toString(16).substring(2)` is used for the random number, which is a base16 representation of a random number between 0 and 1, with the first two characters removed.\n * \n * @param {boolean} safe Defaults to false, if true will use a cryptographically secure random number generator for the random number improving security but reducing performance. If crypto is not available, will use Math.random() instead.\n * @returns string\n * @example\n * basicUID() // => '18d4613e4d2-750bf066ac6158'\n */\nexport function basicUID(safe = false) {\n const rand = safe ? random() : Math.random()\n return Date.now().toString(16)+'-'+rand.toString(16).substring(2)\n}\n\nfunction cryptoUUIDFallback() {\n return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>\n (c ^ Math.random() * 16 >> c / 4).toString(16)\n )\n}\n\n// Taken from https://stackoverflow.com/a/2117523/5437943\nfunction cryptoRandomUUIDFallback() {\n return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>\n (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)\n )\n}\n\n/**\n * Generates a UUID v4\n * - Uses crypto.randomUUID if available\n * - Uses crypto.getRandomValues if available\n * - Uses a fallback if neither is available, which is not safe because it uses Math.random() instead of a cryptographically secure random number generator\n * \n * I'm bad at crypto and bitwise operations, not my cup of tea, so I had to rely on StackOverflow for the fallback: https://stackoverflow.com/a/2117523/5437943\n * \n * @param {boolean} safe Defaults to true, if false will use a fallback that's not cryptographically secure but significantly faster\n * @returns string\n * @example\n * generateUUID() // UUID v4, example 09ed0fe4-8eb6-4c2a-a8d3-a862b7513294\n */\nexport function generateUUID(safe = true) {\n if (!crypto || !safe) return cryptoUUIDFallback()\n if (crypto.randomUUID) return crypto.randomUUID()\n if (crypto.getRandomValues) return cryptoRandomUUIDFallback();\n}\n\n/**\n * Generates a random number between 0 and 1, inclusive of 0 but not inclusive of 1.\n * \n * - Uses crypto.getRandomValues if available\n * - Uses Math.random() if crypto.getRandomValues is not available\n * \n * @returns number\n * @example\n * random() // => 0.123456789\n */\nexport function random() {\n if (!crypto) return Math.random()\n if (crypto.getRandomValues) return crypto.getRandomValues(new Uint32Array(1))[0] / 4294967295 // 2^32 - 1 = 4294967295\n}\n", "/**\n * @module regex\n */\n\n/**\n * Regular expression for matching a YouTube video links and extracting their ID, works with both embed and watch URLs\n */\nexport const RE_YOUTUBE = /(?:youtube\\.com\\/(?:[^\\/]+\\/.+\\/|(?:v|e(?:mbed)?)\\/|.*[?&]v=)|youtu\\.be\\/)([^\"&?\\/ ]{11})/i\n\n/**\n * Regular expression for matching a Vimeo video links and extracting their ID, works with both embed and watch URLs, channels and groups\n */\nexport const RE_VIMEO = /(?:www\\.|player\\.)?vimeo.com\\/(?:channels\\/(?:\\w+\\/)?|groups\\/(?:[^\\/]*)\\/videos\\/|album\\/(?:\\d+)\\/video\\/|video\\/|)(\\d+)(?:[a-zA-Z0-9_\\-]+)?/i\n\n/**\n * Regular expression for matching a video URLs\n */\nexport const RE_VIDEO = /\\/([^\\/]+\\.(?:mp4|ogg|ogv|ogm|webm|avi))\\s*$/i\n\n/**\n * Regular expression for matching a image URLs\n */\nexport const RE_IMAGE = /\\/([^\\/]+\\.(?:jpg|jpeg|png|gif|svg|webp))\\s*$/i\n\n/**\n * Regular expression for matching a URL parameters\n */\nexport const RE_URL_PARAMETER = /([^\\s=&]+)=?([^&\\s]+)?/\n\n/**\n * Regular expression for matching a HTML attribute and tag names, also for matching shortcode attributes and names\n */\nexport const RE_ATTRIBUTES = /\\s*(?:([a-z_]{1}[a-z0-9\\-_]*)=?(?:\"([^\"]+)\"|'([^']+)')*)\\s*/gi\n\n/**\n * Regular expression for matching a single attribute without value\n */\nexport const RE_ATTRIBUTE_WITHOUT_VALUE = /^\\s*([a-z_]{1}[a-z0-9\\-_]*)\\s*$/i\n\n/**\n * Regular expression for matching a single attribute with value\n */\nexport const RE_ATTRIBUTE_WITH_VALUE = /^\\s*([a-z_]{1}[a-z0-9\\-_]*)=(\"[^\"]+\"|'[^']+')\\s*$/i\n\n/**\n * Regular expression for matching the first or last quote of a string used for removing them\n */\nexport const RE_FIRST_OR_LAST_QUOTE = /^[\"']|[\"']$/g\n", "/** @module parsers */\n\nimport { isArray, isObject, isString, stringToType } from './helpers.mjs'\nimport { RE_ATTRIBUTES, RE_ATTRIBUTE_WITHOUT_VALUE, RE_ATTRIBUTE_WITH_VALUE, RE_URL_PARAMETER, RE_FIRST_OR_LAST_QUOTE } from './regex.mjs'\nimport { decodeHTML, encodeHTML } from './dom.mjs'\n\n/**\n * Parse a string of attributes and return an object\n * \n * @param {string} str\n * @returns object\n * @example\n * parseAttributes('button text=\"Click me\" data='{\"key\": \\\"value\"}' class=\"btn btn-primary\"')\n * // => { button: null, text: 'Click me', data: '{\"key\": \"value\"}', class: 'btn btn-primary' }\n */\nexport function parseAttributes(str) {\n\tconst re = RE_ATTRIBUTES\n\tconst reWithoutValue = RE_ATTRIBUTE_WITHOUT_VALUE\n\tconst reHasValue = RE_ATTRIBUTE_WITH_VALUE\n\tconst reReplaceFirstAndLastQuote = RE_FIRST_OR_LAST_QUOTE\n\t\n\tconst res = {}\n\tconst match = str.match(re)\n\n\tfor (let i = 0; i < match.length; i++) {\n\t\tconst m = match[i]\n\t\tif (m === '') continue\n\n\t\tif (reWithoutValue.test(m)) {\n\t\t\tconst [, key] = m.match(reWithoutValue)\n\t\t\tres[key] = null\n\t\t\treWithoutValue.lastIndex = 0\n\t\t} else if (reHasValue.test(m)) {\n\t\t\tconst [, key, value] = m.match(reHasValue)\n\t\t\tres[key] = stringToType(decodeHTML(value.replace(reReplaceFirstAndLastQuote, '')))\n\t\t\treReplaceFirstAndLastQuote.lastIndex = 0\n\t\t\treHasValue.lastIndex = 0\n\t\t}\n\t}\n\n\treturn res\n}\n\n/**\n * Serialize an object of key value pairs into a string of attributes\n * \n * @param {object} obj - The object to serialize\n * @returns {string} of attributes\n * @example\n * serializeAttributes({ button: null, text: 'Click me', data: '{\"key\": \"value\"}', class: 'btn btn-primary' }) // button text=\"Click me\" data=\"{\\\"key\\\": \\\"value\\\"}\" class=\"btn btn-primary\"\n */\nexport function serializeAttributes(obj) {\n\tconst res = []\n\n\tObject.keys(obj).forEach((key) => {\n\t\tlet value = obj[key]\n\t\tif (isObject(value) || isArray(value)) value = JSON.stringify(value)\n\t\tif (isString(value)) value = encodeHTML(value)\n\t\tconst valueString = value === null || value === undefined ? '' : `=\"${value}\"`\n\t\tres.push(`${key}${valueString}`)\n\t})\n\n\treturn res.join(' ')\n}\n\n/**\n * Encodes HTML entities in a string using the following rules:\n * \n * - & (ampersand) becomes &\n * - \" (double quote) becomes "\n * - ' (single quote) becomes '\n * - < (less than) becomes <\n * - > (greater than) becomes >\n * \n * It is different than dom.encodeHTML, which encodes all characters using the browser's DOMParser. This function only encodes the characters listed above and should be used when DOMParser is not available.\n * @see {@link module:dom.encodeHTML}\n * \n * @param {string} str - The string to encode\n * @returns {string} The encoded string\n * @example\n * htmlEncode('Link') // <a href="#">Link</a>\n */\nexport function encodeHtmlEntities(str) {\n\treturn str.replace(/[<>\"'\\&]/g, (m) => {\n\t\tswitch (m) {\n\t\t\tcase '<': return '<'\n\t\t\tcase '>': return '>'\n\t\t\tcase '\"': return '"'\n\t\t\tcase \"'\": return '''\n\t\t\tcase '&': return '&'\n\t\t}\n\t})\n}\n\n/**\n * Decodes HTML entities in a string using the following rules:\n * \n * - & becomes &\n * - " becomes \"\n * - ' becomes '\n * - < becomes <\n * - > becomes >\n * \n * It is different than dom.decodeHTML, which decodes all characters using the browser's DOMParser. This function only decodes the characters listed above and should be used when DOMParser is not available.\n * @see {@link module:dom.decodeHTML}\n * \n * @param {string} str - The string to decode\n * @returns {string} The decoded string\n * @example\n * htmlDecode('<a href="#">Link</a>') // Link\n */\nexport function decodeHtmlEntities(str) {\n\treturn str.replace(/<|>|"|'|&/g, (m) => {\n\t\tswitch (m) {\n\t\t\tcase '<': return '<'\n\t\t\tcase '>': return '>'\n\t\t\tcase '"': return '\"'\n\t\t\tcase ''': return \"'\"\n\t\t\tcase '&': return '&'\n\t\t}\n\t})\n}\n\n\n/**\n * Parses a string of url parameters into an object of key value pairs\n * \n * @param {string} paramString - The string to parse without ? or # and with & as separator\n * @param {boolean} [decode=true] - Whether to decode the values or not\n * @returns {object} of key value pairs\n * @example\n * parseUrlParams('foo=true&baz=555') // { foo: true, baz: 555 }\n * parseUrlParams('foo=bar&baz=qux', false) // { foo: 'true', baz: '555' }\n * parseUrlParams('foo&bar&baz=qux') // { foo: undefined, bar: undefined, baz: 'qux' }\n */\nexport function parseUrlParameters(paramString, decode = true) {\n const res = {}\n\n const paramParts = paramString.split('&')\n paramParts.forEach((part) => {\n const m = part.match(RE_URL_PARAMETER)\n\t\tif (!m) return\n const key = m[1]\n const value = m[2]\n res[key] = value !== undefined && decode ? stringToType(decodeURIComponent(value)) : stringToType(value)\n\t\tRE_URL_PARAMETER.lastIndex = 0\n })\n\n return res\n}\n\n/**\n * Serialize an object of key value pairs into a string of url parameters\n * \n * @param {object} obj - The object to serialize\n * @param {boolean} [encode=true] - Whether to encode the values or not\n * @returns {string} of url parameters\n * @example\n * serializeUrlParams({ foo: true, baz: 555 }) // foo=true&baz=555\n * serializeUrlParams({ bar: undefined, baz: 'qux' }, false) // bar=&baz=qux\n */\nexport function serializeUrlParameters(obj, encode = true) {\n\tconst res = []\n\n\tObject.keys(obj).forEach((key) => {\n\t\tconst value = obj[key]\n\t\tif (value === undefined) return res.push(key)\n\t\tconst encodedValue = encode ? encodeURIComponent(value) : value\n\t\tres.push(`${key}=${encodedValue}`)\n\t})\n\n\treturn res.join('&')\n}\n\n/**\n * Parses a resolution string into a number. Resolution string is in the format of 'width:height', e.g. '16:9' \n * \n * @param {string} res Resolution string. Format is 'width:height', e.g. '16:9', or 'widthxheight', e.g. '16x9', or 'width-height', e.g. '16-9', or 'width/height', e.g. '16/9'\n * @returns number\n * @example\n * parseResolutionString('16:9') // => 1.7777777778\n * parseResolutionString('4:3') // => 1.3333333333\n * parseResolutionString('4x3') // => 1.3333333333\n * parseResolutionString('4-3') // => 1.3333333333\n */\nexport function parseResolutionString(res) {\n const DEFAULT_RESOLUTION = 1.7777777778 // 16:9\n if (!res || !res.length || /16[\\:x\\-\\/]{1}9/i.test(res)) return DEFAULT_RESOLUTION\n const pts = res.split(/\\s?[\\:x\\-\\/]{1}\\s?/i)\n if (pts.length < 2) return DEFAULT_RESOLUTION\n\n const w = parseInt(pts[0])\n const h = parseInt(pts[1])\n\n if (w === 0 || h === 0) return DEFAULT_RESOLUTION\n if (isNaN(w) || isNaN(h)) return DEFAULT_RESOLUTION\n\n return w/h;\n}\n", "/** @module dom */\n\nimport { transformDashToCamelCase, isArray, isString, isObject, isFunction, shallowMerge, percentage } from './helpers.mjs'\nimport { encodeHtmlEntities, decodeHtmlEntities } from './parsers.mjs'\n\n/**\n * Checks if an element is empty\n * \n * @param {HTMLElement} element \n * @returns boolean\n * @example\n * document.body.innerHTML = `\n *
\n *
foo
\n *

`\n * \n * isEmptyElement(document.getElementById('empty-element')) // => true\n * isEmptyElement(document.getElementById('non-empty-element1')) // => false\n * isEmptyElement(document.getElementById('non-empty-element2')) // => false\n */\nexport function isEmptyElement(element) {\n return element.innerHTML.trim() === ''\n}\n\n/**\n * Removes all elements matching a selector from the DOM\n * \n * @param {string|HTMLElement|Element} selector The selector to select elements to remove\n * @param {HTMLElement|Element} [from=document] The element to remove elements from\n * @example\n * document.body.innerHTML = `\n *
\n *
\n *
`\n * `\n * remove('#foo, #bar') // => removes #foo and #bar\n */\nexport function remove(selector, from = document) {\n const elements = query(selector, from)\n for (const element of elements) {\n element.remove()\n }\n}\n\n/**\n * Queries the DOM for a single element and returns it. Substitutes for `document.querySelector(selector)` and JQuery's `$(selector).first()`\n * \n * @param {string|HTMLElement|Element|Array|NodeList} selector The selector to select an element\n * @param {HTMLElement|Element} [from=document] The element to query from\n * @returns {HTMLElement|Element}\n * @example\n * document.body.innerHTML = `\n *
\n *
\n *
`\n * \n * querySingle('#foo') // =>
\n * querySingle(document.getElementById('foo')) // =>
\n * querySingle(document.querySelector('#foo')) // =>
\n */\nexport function querySingle(selector, from = document) {\n if (selector instanceof Element) return selector\n return from.querySelector(selector)\n}\n\n/**\n * Queries the DOM for elements and returns them. Substitutes for `document.querySelectorAll(selector)` and JQuery's `$(selector)`\n * \n * @param {string|HTMLElement|Element|Array|NodeList} selector The selector to select elements\n * @param {HTMLElement|Element} [from=document] The element to query from\n * @returns {Array|NodeList}\n * @example\n * document.body.innerHTML = `\n *
\n *
\n *
`\n * \n * query('#foo') // => [
]\n * query(document.getElementById('foo')) // => [
]\n * query('div') // => [
,
,
]\n */\nexport function query(selector, from = document) {\n if (selector instanceof Array || selector instanceof NodeList) return selector\n if (selector instanceof Element) return [selector]\n if (from instanceof Element || from instanceof Document) return from.querySelectorAll(selector)\n if (isString(from)) from = query(from)\n if (!from instanceof Array && !from instanceof NodeList) return []\n const res = []\n for (const element of from) {\n res.push(...element.querySelectorAll(selector))\n }\n return res\n}\n\n/**\n * Sets element styles from passed object of styles. Can also transform dash-case to camelCase for CSS properties\n * \n * @param {HTMLElement} element The element to set styles on\n * @param {object} styles The object of styles to set\n * @param {boolean} transform Whether to transform dash-case to camelCase for CSS properties\n * @example\n * css(document.getElementById('foo'), { 'background-color': 'red', 'font-size': '16px' }, true) // => sets background-color and font-size\n * css(document.getElementById('foo'), { backgroundColor: 'red', fontSize: '16px' }) // => sets background-color and font-size\n */\nexport function css(element, styles, transform = false) {\n if (!element || !styles) return\n for (let property in styles) {\n if (transform) property = transformDashToCamelCase(property)\n element.style[property] = styles[property]\n }\n}\n\n/**\n * Decodes HTML entities in a string using the browser's DOMParser. If the DOMParser is not available, it uses a regular expression to decode the basic entities.\n * \n * @see {@link module:parsers.decodeHtmlEntities}\n * \n * @param {string} html The HTML string to decode\n * @returns {string} The decoded HTML string\n * @example\n * decodeHTML('<div>foo</div>') // => '
foo
'\n * decodeHTML('<div>foo</div><div>bar</div>') // => '
foo
bar
'\n */\nexport function decodeHTML(html) {\n if (typeof document === 'undefined') return decodeHtmlEntities(html)\n const txt = document.createElement('textarea')\n txt.innerHTML = html\n const res = txt.value\n txt.remove()\n return res\n}\n\n/**\n * Encodes HTML entities in a string using the browser's DOMParser. If the DOMParser is not available, it uses a regular expression to encode the basic entities.\n * \n * @see {@link module:parsers.encodeHtmlEntities}\n * \n * @param {string} html The HTML string to encode\n * @returns {string} The encoded HTML string\n * @example\n * encodeHTML('
foo
') // => '<div>foo</div>'\n * encodeHTML('
foo
bar
') // => '<div>foo</div><div>bar</div>'\n */\nexport function encodeHTML(html) {\n if (typeof document === 'undefined') return encodeHtmlEntities(html)\n const txt = document.createElement('textarea')\n txt.textContent = html\n const res = txt.innerHTML\n txt.remove()\n return res\n}\n\n/**\n * Inserts an element before another element\n * \n * @param {HTMLElement} targetElement The element to insert before\n * @param {HTMLElement} newElement The element to insert\n * @example\n * const target = document.getElementById('target')\n * const newElement = document.createElement('div')\n * newElement.id = 'newElement'\n * insertBeforeElement(target, newElement)\n * //
\n * //
\n */\nexport function insertBeforeElement(targetElement, newElement) {\n if (!targetElement || !newElement) return\n targetElement.parentNode.insertBefore(newElement, targetElement);\n}\n\n/**\n * Toggles an attribute value on an element\n * \n * @param {HTMLElement} element The element to toggle the attribute on\n * @param {string} attribute The attribute to toggle\n * @param {string} on Default: 'true'\n * @param {string} off Default: 'false'\n * @example\n * toggleAttributeValue(element, 'aria-expanded', 'true', 'false')\n * toggleAttributeValue(element, 'aria-expanded')\n */\nexport function toggleAttributeValue(element, attribute, on = 'true', off = 'false') {\n if (!element.hasAttribute(attribute)) return\n\n if (element.getAttribute(attribute) === on) {\n element.setAttribute(attribute, off)\n } else {\n element.setAttribute(attribute, on)\n }\n}\n\n/**\n * Converts a duration string to milliseconds integer\n * \n * @param {string} duration The duration string to convert, e.g. '1s', '100ms', '0.5s'\n * @returns {number} The duration in milliseconds\n * @example\n * convertToMilliseconds('1s') // 1000\n * convertToMilliseconds('100ms') // 100\n * convertToMilliseconds('0.5s') // 500\n * convertToMilliseconds('0.5') // 0\n * convertToMilliseconds('foo') // 0\n */\nexport function cssTimeToMilliseconds(duration) {\n const regExp = new RegExp('([0-9.]+)([a-z]+)', 'i')\n const matches = regExp.exec(duration)\n if (!matches) return 0\n \n const unit = matches[2]\n switch (unit) {\n case 'ms':\n return parseFloat(matches[1])\n case 's':\n return parseFloat(matches[1]) * 1000\n default:\n return 0\n }\n}\n\n/**\n * Returns a map of transition properties and durations\n * \n * @param {HTMLElement} element The element to get the transition properties and durations from\n * @returns {object} A map of transition properties and durations\n * @example\n * getTransitionDurations(element) // { height: 1000 } if transition in CSS is set to 'height 1s'\n * getTransitionDurations(element) // { height: 500, opacity: 1000 } if transition in CSS is set to 'height 0.5s, opacity 1s'\n */\nexport function getTransitionDurations(element) {\n if (!element) {}\n const styles = getComputedStyle(element)\n const transitionProperties = styles.getPropertyValue('transition-property').split(',')\n const transitionDurations = styles.getPropertyValue('transition-duration').split(',')\n \n const map = {}\n \n for (let i = 0; i < transitionProperties.length; i++) {\n const property = transitionProperties[i].trim()\n map[property] = transitionDurations.hasOwnProperty(i) ? cssTimeToMilliseconds(transitionDurations[i].trim()) : null\n }\n \n return map\n}\n\n/**\n * Check a list of elements if any of them matches a selector\n * \n * @param {Array|NodeList|HTMLElement} elements The elements to check\n * @param {string} selector The selector to check\n * @returns {boolean} True if any of the elements matches the selector, false otherwise\n * @example\n * document.body.innerHTML = `\n *
\n *
\n *
`\n * \n * matchesAny(document.querySelectorAll('div'), '#foo') // => true\n * matchesAny(document.querySelectorAll('div'), '#qux') // => false\n */\nexport function matchesAny(elements, selector) {\n if (!elements || !selector || !elements.length) return false\n if (elements instanceof Element) elements = [elements]\n if (isString(elements)) elements = query(elements)\n for (const element of elements) {\n if (element.matches(selector)) return true\n }\n return false\n}\n\n/**\n * Check a list of elements if all of them matches a selector\n * \n * @param {Array|NodeList|HTMLElement} elements The elements to check\n * @param {string} selector The selector to check\n * @returns {boolean} True if all of the elements matches the selector, false otherwise\n * @example\n * document.body.innerHTML = `\n *
\n *
\n *
`\n * \n * matchesAll(document.querySelectorAll('div'), 'div') // => true\n * matchesAll(document.querySelectorAll('div'), '#foo') // => false\n */\nexport function matchesAll(elements, selector) {\n if (!elements || !selector || !elements.length) return false\n if (elements instanceof Element) elements = [elements]\n if (isString(elements)) elements = query(elements)\n for (const element of elements) {\n if (!element.matches(selector)) return false\n }\n return true\n}\n\n\n/**\n * Detaches an element from the DOM and returns it\n * \n * @param {HTMLElement} element The element to detach\n * @example\n * detachElement(element)\n * // => element\n * console.log(element.parentNode) // => null\n */\nexport function detachElement(element) {\n if (element && element.parentNode) {\n element.parentNode.removeChild(element);\n }\n return element\n}\n\n/**\n * Gets table data from a table element, a simple regular table element, or a table like structure.\n * Useful for scraping data.\n * \n * @param {string} selector The selector to select the table element\n * @param {Array|string|null} headers The headers to use for the data. If 'auto' is passed, the row containing th or the first row will be used as headers\n * @param {string} [rowSelector='tr'] The selector to select the rows\n * @param {string} [cellSelector='td'] The selector to select the cells\n * @returns {Array} An array of objects with the properties as keys and the cell values as values\n * @example\n * document.body.innerHTML = `\n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n *
FooBar
Foo 1Bar 1
Foo 2Bar 2
`\n * \n * getTableData('#table', ['foo', 'bar'])\n * // => [\n * // { foo: 'Foo 1', bar: 'Bar 1' },\n * // { foo: 'Foo 2', bar: 'Bar 2' }\n * // ]\n */\nexport function getTableData(selector, headers, rowSelector = 'tr', cellSelector = 'td', headerCellSelector = 'th') {\n const table = typeof selector === 'string' ? document.querySelector(selector) : selector\n const res = []\n const rows = table.querySelectorAll(rowSelector)\n let start = 0\n\n function iterateHeaders(arr) {\n if (!arr || !arr.length) return\n const res = []\n for (let i = 0; i < arr.length; i++) {\n res.push(arr[i].textContent.trim())\n }\n return res\n }\n\n if (headers && isString(headers) && headers === 'auto') {\n let headerCells = table.querySelectorAll(headerCellSelector)\n \n if (headerCells && headerCells.length) {\n headers = iterateHeaders(headerCells)\n } else {\n headers = iterateHeaders(rows[0].querySelectorAll(cellSelector))\n start = 1\n }\n }\n\n for (let i = start; i < rows.length; i++) {\n const row = rows[i]\n const cells = row.querySelectorAll(cellSelector)\n if (!cells || !cells.length) continue\n\n let rowData = []\n if (headers && isArray(headers) && headers.length) {\n rowData = {}\n for (let j = 0; j < headers.length; j++) {\n rowData[headers[j]] = cells[j] ? cells[j].textContent.trim() : null\n }\n } else {\n for (let j = 0; j < cells.length; j++) {\n rowData.push(cells[j].textContent.trim())\n }\n }\n res.push(rowData)\n }\n return res\n}\n\n/**\n * Parses HTML string to a DOM Node\n * \n * @param {string} html The HTML string to parse\n * @param {boolean} [allChildren=false] If true, all children of the body will be returned, otherwise only the first child\n * @returns {Node} The parsed DOM Node\n * @example\n * parseDOM('
foo
') // =>
foo
\n * parseDOM('
foo
bar
', true) // => NodeList(2)\u00A0[div, div]\n * parseDOM(document.getElementById('foo')) // =>
\n * parseDOM(document.querySelectorAll('div')) // => NodeList(2)\u00A0[div, div]\n */\nexport function parseDOM(html, allChildren) {\n if (html instanceof Element || html instanceof NodeList) return html\n const parser = new DOMParser()\n const doc = parser.parseFromString(html, 'text/html')\n return !allChildren ? doc.body.firstChild : doc.body.childNodes\n}\n\n/**\n * Loads an image form a provided source url and calls a callback when it's loaded\n * \n * @param {string} src The source url of the image\n * @param {Function} [callback] The callback to call when the image is loaded\n * @example\n * loadImage('https://example.com/image.png', () => {\n * console.log('Image loaded')\n * })\n */\nexport function loadImage(src, callback) {\n const img = new Image()\n if (callback)\n img.addEventListener('load', callback, false);\n img.src = src\n}\n\n/**\n * Delegate DOM events using MutationObserver with a fallback to document.addEventListener\n * \n * @param {string} selector The selector to select the elements to delegate the event to\n * @param {string} eventType The event type to delegate, like `click`\n * @param {Function} handler The handler to call when the event is triggered.\n * @returns {MutationObserver | null} The MutationObserver instance\n * @example\n * delegateEvent('.foo', 'click', (e, target) => {\n * console.log('Clicked on', target)\n * })\n */\nexport function delegateEvent(selector, eventType, handler) {\n if (typeof MutationObserver === 'undefined') {\n document.addEventListener(eventType, (e) => {\n const target = e.target.closest(selector)\n if (target) handler(e, target)\n })\n\n return null\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (!(node instanceof HTMLElement)) continue\n if (!node.matches(selector)) continue\n node.addEventListener(eventType, (e) => {\n handler(e, e.currentTarget)\n })\n }\n }\n })\n\n for (const node of document.querySelectorAll(selector)) {\n node.addEventListener(eventType, (e) => {\n handler(e, e.currentTarget)\n })\n }\n\n observer.observe(document.body, { childList: true, subtree: true })\n return observer\n}\n\n/**\n * Run a handler on selected elements and on elements added to the DOM with the same selector, \n * or can be delegateEvent alias.\n * \n * @param {string} selector The selector to select the elements to run the handler on\n * @param {string | Function} eventTypeOrHandler The event type to delegate, like `click`, or the handler to call on every element\n * @param {Function} [handler] The handler to call when the event is triggered.\n * @returns {MutationObserver | null} The MutationObserver instance\n * @see delegateEvent\n * @example\n * on('.foo', (el) => {\n * console.log('Element', el, 'added to the DOM')\n * })\n * \n * on('.foo', 'click', (e, target) => {\n * console.log('Clicked on', target)\n * })\n */\nexport function on(selector, eventTypeOrHandler, handler) {\n if (isString(eventTypeOrHandler)) {\n return delegateEvent(selector, eventTypeOrHandler, handler)\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (!(node instanceof HTMLElement)) continue\n if (!node.matches(selector)) continue\n eventTypeOrHandler(node)\n }\n }\n })\n\n for (const node of document.querySelectorAll(selector)) {\n eventTypeOrHandler(node)\n }\n\n observer.observe(document.body, { childList: true, subtree: true })\n\n return observer\n}\n\n/**\n * Adds one listener to multiple events\n * \n * @param {string|HTMLElement|NodeList} elements The elements or a selector for elements to add the event listeners to\n * @param {string|Array} events The event types to add the event listeners for, like `click mouseenter`\n * @param {Function} handler The handler to call when the event is triggered.\n * @param {object} [options] The options to pass to the event listeners\n * @example\n * addListenerForEvents('.foo', 'click mouseenter', (e) => { console.log(e.type) })\n */\nexport function addListenerForEvents(elements, events, handler, options) {\n if (elements instanceof Element) elements = [elements]\n if (typeof elements === 'string') elements = query(elements)\n\n const eventTypes = isArray(events) ? events : events.split(' ')\n for (const element of elements) {\n for (const eventType of eventTypes) {\n element.addEventListener(eventType, handler, options)\n }\n }\n}\n\n/**\n * Removes one listener from multiple registered events\n * \n * @param {string|HTMLElement|NodeList} elements The elements or a selector for elements to remove the event listeners from\n * @param {string|Array} events The event types to remove the event listeners for, like `click mouseenter`\n * @param {Function} handler The handler to remove\n * @param {object} [options] The options to pass to the event listeners\n * @example\n * removeListenerForEvents('.foo', 'click mouseenter', (e) => { console.log(e.type) })\n */\nexport function removeListenerForEvents(elements, events, handler, options) {\n if (elements instanceof Element) elements = [elements]\n if (typeof elements === 'string') elements = query(elements)\n\n const eventTypes = isArray(events) ? events : events.split(' ')\n for (const element of elements) {\n for (const eventType of eventTypes) {\n element.removeEventListener(eventType, handler, options)\n }\n }\n}\n\n/**\n * Resizes an element to cover its parent element while maintaining the aspect ratio\n * \n * @param {string|HTMLElement|NodeList} elements The elements or a selector for elements to resize\n * @param {number} [ratio=1] The ratio to maintain\n * @param {number} [offset=0] An offset to add to the parent element's width and height\n * @example\n * proportionalParentCoverResize('.foo', 16/9, 10)\n */\nexport function proportionalParentCoverResize(elements, ratio = 1, offset = 0) {\n if (elements instanceof Element) elements = [elements]\n if (typeof elements === 'string') elements = query(elements)\n\n for (const element of elements) {\n const h = element.parentNode.offsetHeight + offset\n const w = element.parentNode.offsetWidth + offset\n\n if (ratio > w/h) {\n element.style.width = h*ratio + 'px'\n element.style.height = h + 'px'\n } else {\n element.style.width = w + 'px'\n element.style.height = w/ratio + 'px'\n }\n }\n}\n\n/**\n * If provided element is visible. Checks if the element is not visibility hidden or display none, has no opacity, and has a width and height.\n * \n * @param {HTMLElement} element The element to check\n * @returns {boolean} True if the element is visible, false otherwise\n * \n * @example\n * isVisible(document.getElementById('foo'))\n */\nexport function isVisible(element) {\n if (!element) return false;\n const computedStyle = getComputedStyle(element);\n if (computedStyle.getPropertyValue('display') === 'none') return false;\n if (element.getAttribute('hidden') !== null || computedStyle.getPropertyValue('visibility') === 'hidden' || computedStyle.getPropertyValue('opacity') == \"0\") return false;\n return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length)\n}\n\n/**\n * Swipe event handler\n * \n * @param {HTMLElement} element The element to listen for swipe gestures on\n * @param {object | Function} callback The callback to call when a swipe gesture is detected or the options object with the callback, threshold, and timeThreshold\n * @param {number} [threshold=150] The threshold in pixels to trigger the callback.\n * @param {number} [timeThreshold=0] The threshold in milliseconds to trigger the callback. Defaults to 0, which means the callback will be called regardless of the time it took to swipe.\n * @returns {object} The destroy method to remove the event listeners\n * @example\n * swipe(document.getElementById('foo'), (e) => {\n * console.log(e.direction)\n * console.log(e.deltaX)\n * console.log(e.deltaY)\n * console.log(e.startX)\n * console.log(e.startY)\n * console.log(e.endX)\n * console.log(e.endY)\n * console.log(e.threshold)\n * console.log(e.type)\n * console.log(e.target)\n * console.log(e.horizontal)\n * console.log(e.vertical)\n * console.log(e.horizontalDirection)\n * console.log(e.verticalDirection)\n * console.log(e.timeElapsed)\n * console.log(e.timeThreshold)\n * })\n * \n * element.addEventListener('swipe', (e) => { ... })\n * element.addEventListener('swipestart', (e) => { ... })\n * element.addEventListener('swipeend', (e) => { ... })\n */\nexport function swipe(element, callback, threshold = 150, timeThreshold = 0) {\n let startX = 0\n let startY = 0\n let endX = 0\n let endY = 0\n let startTime = 0\n let endTime = 0\n\n if (isObject(callback)) {\n const options = callback\n callback = options.callback\n threshold = options.threshold || threshold\n timeThreshold = options.timeThreshold || timeThreshold\n }\n\n if (!element) return\n if (element.getAttribute('swipe-enabled') === 'true') return\n element.setAttribute('swipe-enabled', 'true')\n\n const handleStart = function(e) {\n const carrier = e.touches ? e.touches[0] : e\n startX = carrier.clientX\n startY = carrier.clientY\n startTime = Date.now();\n element.dispatchEvent(new CustomEvent('swipestart', { detail: { target: element, startX, startY, startTime } }))\n }\n\n const handleEnd = function(e) {\n const carrier = e.changedTouches ? e.changedTouches[0] : e\n endX = carrier.clientX\n endY = carrier.clientY\n endTime = Date.now();\n handleSwipeGesture()\n element.dispatchEvent(new CustomEvent('swipeend', { detail: { target: element, startX, startY, startTime, endX, endY, endTime } }))\n }\n\n const handleSwipeGesture = function() {\n const deltaX = Math.abs(endX - startX)\n const deltaY = Math.abs(endY - startY)\n const horizontal = deltaX > threshold\n const vertical = deltaY > threshold\n const left = endX < startX\n const up = endY < startY\n const direction = []\n const timeElapsed = endTime - startTime;\n \n if (horizontal) direction.push(left ? 'left' : 'right')\n if (vertical) direction.push(up ? 'up' : 'down')\n\n let condition = direction.length && callback\n if (timeThreshold) condition = condition && timeElapsed <= timeThreshold\n \n if (condition) {\n const res = {\n target: element,\n deltaX: deltaX,\n deltaY: deltaY,\n startX: startX,\n startY: startY,\n endX: endX,\n endY: endY,\n threshold: threshold,\n horizontal: horizontal,\n vertical: vertical,\n horizontalDirection: left ? 'left' : 'right',\n verticalDirection: up ? 'up' : 'down',\n direction: direction.length === 1 ? direction[0] : direction,\n timeElapsed: timeElapsed,\n timeThreshold: timeThreshold\n }\n\n callback(res)\n element.dispatchEvent(new CustomEvent('swipe', { detail: res })) \n }\n }\n\n element.addEventListener('touchstart', handleStart)\n element.addEventListener('touchend', handleEnd)\n element.addEventListener('mousedown', handleStart)\n element.addEventListener('mouseup', handleEnd)\n\n return {\n destroy: function() {\n element.removeEventListener('touchstart', handleStart)\n element.removeEventListener('touchend', handleEnd)\n element.removeEventListener('mousedown', handleStart)\n element.removeEventListener('mouseup', handleEnd)\n }\n }\n}\n\n/**\n * Alias for swipe\n * \n * @see swipe\n * @deprecated Use swipe instead\n */\nexport const onSwipe = swipe\n\n/**\n * Drag event handler\n * \n * @param {HTMLElement} element The element to listen for drag gestures on\n * @param {object | Function} opts The options object or the callback to call when a drag gesture is detected\n * @param {boolean} [opts.inertia=false] Whether to enable inertia\n * @param {boolean} [opts.bounce=false] Whether to enable bounce when inertia is enabled\n * @param {number} [opts.friction=0.9] The friction to apply when inertia is enabled\n * @param {number} [opts.bounceFactor=0.2] The bounce factor to apply when bounce is enabled\n * @param {boolean} [opts.preventDefaultTouch=true] Whether to prevent the default touch behavior\n * @param {Function} [opts.callback] The callback to call when a drag gesture is detected\n * @returns {object} The destroy method to remove the event listeners\n * @example\n * drag(document.getElementById('foo'), (e) => {\n * console.log(e.x)\n * console.log(e.y)\n * console.log(e.relativeX)\n * console.log(e.relativeY)\n * console.log(e.xPercentage)\n * console.log(e.yPercentage)\n * console.log(e.velocityX)\n * console.log(e.velocityY)\n * console.log(e.prevX)\n * console.log(e.prevY)\n * })\n * \n * element.addEventListener('drag', (e) => { ... })\n * element.addEventListener('dragstart', (e) => { ... })\n * element.addEventListener('dragend', (e) => { ... })\n * element.addEventListener('draginertia', (e) => { ... })\n * element.addEventListener('draginertiaend', (e) => { ... })\n */\nexport function drag(element, opts) {\n if (!element || !(element instanceof Element)) return\n if (element.getAttribute('drag-enabled') === 'true') return\n\n let x = 0\n let y = 0\n let prevX = 0\n let prevY = 0\n let velocityX = 0\n let velocityY = 0\n let dragging = false\n let rect = null\n let inertiaId = null\n\n const options = {\n inertia: false,\n bounce: false,\n friction: 0.9,\n bounceFactor: 0.2,\n callback: null,\n preventDefaultTouch: true\n }\n\n if (isFunction(opts)) {\n options.callback = opts\n } else if (isObject(opts)) {\n shallowMerge(options, opts)\n }\n\n options.friction = Math.abs(options.friction)\n options.bounceFactor = Math.abs(options.bounceFactor)\n\n element.setAttribute('drag-enabled', 'true')\n element.setAttribute('dragging', 'false')\n\n const calcPageRelativeRect = function() {\n const origRect = element.getBoundingClientRect()\n const rect = {\n top: origRect.top + window.scrollY,\n left: origRect.left + window.scrollX,\n width: origRect.width,\n height: origRect.height\n }\n\n return rect\n }\n rect = calcPageRelativeRect()\n\n const handleStart = function(e) {\n setXY(e)\n dragging = true\n rect = calcPageRelativeRect()\n element.setAttribute('dragging', 'true')\n if (inertiaId) {\n cancelAnimationFrame(inertiaId)\n inertiaId = null\n }\n const event = new CustomEvent('dragstart', { detail: getDetail() })\n element.dispatchEvent(event)\n }\n\n const handleMove = function(e) {\n if (!dragging) return\n setXY(e)\n velocityX = x - prevX\n velocityY = y - prevY\n const detail = getDetail()\n if (options.callback) options.callback(detail)\n const event = new CustomEvent('drag', { detail: detail })\n element.dispatchEvent(event)\n }\n\n const handleEnd = function() {\n dragging = false\n element.setAttribute('dragging', 'false')\n if (options.inertia) inertiaId = requestAnimationFrame(inertia)\n const event = new CustomEvent('dragend', { detail: getDetail() })\n element.dispatchEvent(event)\n }\n\n const setXY = function(e) {\n const carrier = e.touches ? e.touches[0] : e\n if (e.touches && options.preventDefaultTouch) e.preventDefault()\n prevX = x\n prevY = y\n x = carrier.pageX\n y = carrier.pageY\n }\n\n const getDetail = function() {\n const relativeX = x - rect.left\n const relativeY = y - rect.top\n const xPercentage = percentage(relativeX, rect.width)\n const yPercentage = percentage(relativeY, rect.height)\n\n const detail = {\n target: element,\n x: x,\n y: y,\n relativeX: relativeX,\n relativeY: relativeY,\n xPercentage: xPercentage,\n yPercentage: yPercentage,\n velocityX: velocityX,\n velocityY: velocityY,\n prevX: prevX,\n prevY: prevY\n }\n\n if (xPercentage < 0) detail.xPercentage = 0\n if (xPercentage > 100) detail.xPercentage = 100\n if (yPercentage < 0) detail.yPercentage = 0\n if (yPercentage > 100) detail.yPercentage = 100\n\n return detail\n }\n\n const inertia = function() {\n x += velocityX\n y += velocityY\n velocityX *= options.friction\n velocityY *= options.friction\n\n if (options.bounce) {\n if (x < rect.left) {\n x = rect.left\n velocityX *= -options.bounceFactor\n }\n if (x > rect.width + rect.left) {\n x = rect.width + rect.left\n velocityX *= -options.bounceFactor\n }\n if (y < rect.top) {\n y = rect.top\n velocityY *= -options.bounceFactor\n }\n if (y > rect.height + rect.top) {\n y = rect.height + rect.top\n velocityY *= -options.bounceFactor\n }\n }\n\n if (Math.abs(velocityX) < 0.1) velocityX = 0\n if (Math.abs(velocityY) < 0.1) velocityY = 0\n\n const detail = getDetail()\n\n if (velocityX !== 0 || velocityY !== 0) {\n if (options.callback) options.callback(detail)\n const event = new CustomEvent('draginertia', { detail: detail })\n element.dispatchEvent(event)\n inertiaId = requestAnimationFrame(inertia)\n } else {\n inertiaId = null\n if (options.callback) options.callback(detail)\n const event = new CustomEvent('draginertiaend', { detail: detail })\n element.dispatchEvent(event)\n }\n }\n\n element.addEventListener('mousedown', handleStart)\n element.addEventListener('mousemove', handleMove)\n element.addEventListener('mouseup', handleEnd)\n element.addEventListener('touchstart', handleStart)\n element.addEventListener('touchmove', handleMove)\n element.addEventListener('touchend', handleEnd)\n\n return {\n destroy: function() {\n element.removeEventListener('mousedown', handleStart)\n element.removeEventListener('mousemove', handleMove)\n element.removeEventListener('mouseup', handleEnd)\n element.removeEventListener('touchstart', handleStart)\n element.removeEventListener('touchmove', handleMove)\n element.removeEventListener('touchend', handleEnd)\n\n if (inertiaId) {\n cancelAnimationFrame(inertiaId)\n inertiaId = null\n }\n }\n }\n}\n\n/**\n * Alias for drag\n * \n * @see drag\n * @deprecated Use drag instead\n */\nexport const onDrag = drag\n", "/** @module browser */\n\nimport { isEmpty, isFunction } from './helpers.mjs'\nimport { css } from './dom.mjs'\nimport { parseUrlParameters } from './parsers.mjs'\n\nexport function isUserAgentIOS(str) {\n return /iPad|iPhone|iPod/i.test(str)\n}\n\nexport function isUserAgentMobile(str) {\n return /\\b(BlackBerry|webOS|iPhone|IEMobile)\\b/i.test(str) ||\n /\\b(Android|Windows Phone|iPad|iPod)\\b/i.test(str)\n}\n\nexport function isUserAgentSafari(str) {\n return /^((?!chrome|android|crios|fxios).)*safari/i.test(str)\n}\n\n/**\n * Check if the device is an iOS device\n * \n * @returns boolean True if the device is an iOS device, false otherwise\n */\nexport function isIOS() {\n return isUserAgentIOS(navigator.userAgent) && 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 1\n}\n\n/**\n * Check if the device is a mobile device\n * \n * @returns boolean True if the device is a mobile device, false otherwise\n */\nexport function isMobile() {\n if ('maxTouchPoints' in navigator) return navigator.maxTouchPoints > 0\n\n if ('matchMedia' in window) return !!matchMedia('(pointer:coarse)').matches\n\n if ('orientation' in window) return true\n\n return isUserAgentMobile(navigator.userAgent)\n}\n\n/**\n * Check if the browser is Safari\n *\n * @returns boolean True if the browser is Safari, false otherwise\n */\nexport function isSafari() {\n if (navigator.hasOwnProperty('vendor')) /apple/i.test(navigator.vendor)\n return isUserAgentSafari(navigator.userAgent)\n}\n\n/**\n * Check if the browser is Safari on iOS\n * \n * @returns boolean True if the browser is Safari on iOS, false otherwise\n */\nexport function isIOSSafari() {\n return isIOS() && isSafari()\n}\n\n/**\n * A wrapper for the matchMedia function, cause with `matchMedia` you can only either add a listener or check the media query\n * this function does both.\n * \n * @param {string} query The media query to check\n * @param {function} [callback] The callback function to call when the media query changes\n * @returns {boolean} The result of the media query\n * \n * @example\n * mediaMatcher('(min-width: 768px)', (matches) => {\n * if (matches) {\n * // Do something\n * } else {\n * // Do something else\n * }\n * })\n * \n * // Or\n * \n * const isDesktop = mediaMatcher('(min-width: 768px)')\n */\nexport function mediaMatcher(query, callback) {\n if (isFunction(callback)) {\n matchMedia(query).addEventListener('change', (e) => {\n callback(e.matches)\n })\n\n const mql = matchMedia(query)\n callback(mql.matches)\n\n return mql.matches\n }\n\n return matchMedia(query).matches\n}\n\n/**\n * Get the scrollbar width\n * \n * When preventing scroll with html overflow hidden the scroll bar will disappear and the whole page will shift (if the scroll bar is visible that is).\n * To substitute for the scrollbar width we can add a padding to the body element.\n * \n * @returns {number} The scrollbar width\n * \n * @example\n * const scrollbarWidth = getScrollbarWidth() // 15 (on MacOS X Safari)\n */\nexport function getScrollbarWidth() {\n const scrollDiv = document.createElement('div')\n \n css(scrollDiv, {\n width: '100px',\n height: '100px',\n position: 'absolute',\n left: '-9999px',\n zIndex: '0',\n overflowX: 'hidden',\n overflowY: 'scroll'\n })\n\n document.body.appendChild(scrollDiv)\n const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth\n document.body.removeChild(scrollDiv)\n return scrollbarWidth\n}\n\n/**\n * Check if the vertical scrollbar is visible\n * \n * @param {number} [scrollbarWidth] The width of the scrollbar, defaults to getScrollbarWidth()\n * @returns {boolean} True if the vertical scrollbar is visible, false otherwise\n */\nexport function hasVerticalScrollbarVisible(scrollbarWidth) {\n if (scrollbarWidth === undefined) scrollbarWidth = getScrollbarWidth()\n return window.innerHeight < document.body.scrollHeight && scrollbarWidth > 0\n}\n\n/**\n * Check if the horizontal scrollbar is visible\n * \n * @param {number} [scrollbarWidth] The width of the scrollbar, defaults to getScrollbarWidth()\n * @returns {boolean} True if the horizontal scrollbar is visible, false otherwise\n */\nexport function hasHorizontalScrollbarVisible(scrollbarWidth) {\n if (scrollbarWidth === undefined) scrollbarWidth = getScrollbarWidth()\n return window.innerWidth < document.body.scrollWidth && scrollbarWidth > 0\n}\n\n/**\n * Disable the scroll on the page.\n * \n * @param {number} [shift=0] If greater than 0 the body will be shifted to the left by the width of the scrollbar, getScrollbarWidth() is used to provide this value \n */\nexport function disableScroll(shift) {\n const body = document.body\n if (shift && hasVerticalScrollbarVisible(shift)) body.style.paddingRight = `${shift}px`\n body.style.overflow = 'hidden'\n}\n\n/**\n * Enable the scroll on the page.\n * \n * @param {boolean} [shift=0] If greater than 0 the body will be shifted back to the left by the width of the scrollbar, getScrollbarWidth() is used to provide this value\n */\nexport function enableScroll(shift) {\n const body = document.body\n body.style.overflow = ''\n if (shift) body.style.paddingRight = ''\n}\n\n/**\n * Parses a string of url query parameters into an object of key value pairs. Converts the values to the correct type.\n * \n * @param {string} [entryQuery] - Optional query string to parse, without the starting ?, defaults to window.location.search without the starting ?\n * @returns {object} of key value pairs\n * @example\n * // url: https://example.com/?test&foo=bar&baz=qux\n * getQueryProperties() // { test: undefined, foo: 'bar', baz: 'qux' }\n */\nexport function getQueryProperties(entryQuery) {\n const query = entryQuery ? entryQuery : window.location.search.replace('?', '')\n if (isEmpty(query)) return {}\n\n return parseUrlParameters(query)\n}\n\n/**\n * Parses a string of url hash parameters into an object of key value pairs. Converts the values to the correct type.\n * \n * @param {string} [entryHash] - Optional hash string to parse, without the starting #, defaults to window.location.hash without the starting #\n * @returns {object} of key value pairs\n * @example\n * // url: https://example.com/#test&foo=bar&baz=qux\n * getHashProperties() // { test: undefined, foo: 'bar', baz: 'qux' }\n */\nexport function getHashProperties(entryHash) {\n const hash = entryHash ? entryHash : window.location.hash.replace('#', '')\n if (isEmpty(hash)) return {}\n\n return parseUrlParameters(hash)\n}\n\nfunction onHashChange(callback) {\n const hash = window.location.hash.replace('#', '')\n if (!isEmpty(hash)) callback(hash)\n}\n\n/**\n * Add a callback function to the hash change event\n * \n * @param {function} callback - The callback function to call when the hash changes\n * @param {string} [single] - Optional string to make sure the listener is initialized only once, defaults to window[single] which is set to true after the first call\n * @example\n * hashChange((hash) => {\n * // Do something with the hash\n * })\n */\nexport function hashChange(callback, single) {\n onHashChange(callback)\n \n if (single && window[single]) return\n if (single) window[single] = true\n \n window.addEventListener('hashchange', () => {\n onHashChange(callback)\n })\n}\n", "import { generateActionButton } from './buttons.js';\nimport { isArray, stringToType, isMobile, parseResolutionString, proportionalParentCoverResize, percentage, fixed } from 'book-of-spells';\n\nexport class SuperVideoBackground {\n constructor(elem, params, id, uid, type, factoryInstance) {\n if (!id) return;\n this.is_mobile = isMobile();\n this.type = type;\n this.id = id;\n this.factoryInstance = factoryInstance;\n\n this.element = elem;\n this.playerElement = null;\n this.uid = uid;\n this.element.setAttribute('data-vbg-uid', uid);\n\n this.buttons = {};\n this.isIntersecting = false;\n\n this.paused = false; // user requested pause. used for blocking intersection softPlay\n this.muted = false;\n this.currentState = 'notstarted';\n\n this.initialPlay = false;\n this.initialVolume = false;\n\n this.volume = 1;\n\n this.params = {};\n\n const DEFAULTS = {\n 'pause': false, //deprecated\n 'play-button': false,\n 'mute-button': false,\n 'autoplay': true,\n 'muted': true,\n 'loop': true,\n 'mobile': true,\n 'load-background': false,\n 'resolution': '16:9',\n 'inline-styles': true,\n 'fit-box': false,\n 'offset': 100, // since showinfo is deprecated and ignored after September 25, 2018. we add +100 to hide it in the overflow\n 'start-at': 0,\n 'end-at': 0,\n 'poster': null,\n 'always-play': false,\n 'volume': 1,\n 'no-cookie': true,\n 'force-on-low-battery': false,\n 'lazyloading': false,\n 'title': 'Video background'\n };\n\n this.params = this.parseProperties(params, DEFAULTS, this.element, ['data-ytbg-', 'data-vbg-']);\n\n //pause deprecated\n if (this.params.pause) {\n this.params['play-button'] = this.params.pause;\n }\n\n this.params.resolution_mod = parseResolutionString(this.params.resolution);\n\n this.muted = this.params.muted;\n\n this.volume = this.params.volume;\n\n this.currentTime = this.params['start-at'] || 0;\n this.duration = this.params['end-at'] || 0;\n this.percentComplete = 0;\n if (this.params['start-at']) this.percentComplete = this.timeToPercentage(this.params['start-at']);\n\n this.buildWrapperHTML();\n\n if (this.is_mobile && !this.params.mobile) return;\n\n if (this.params['play-button']) {\n generateActionButton(this, {\n name: 'playing',\n className: 'play-toggle',\n innerHtml: '',\n initialState: !this.paused,\n stateClassName: 'paused',\n condition_parameter: 'paused',\n stateChildClassNames: ['fa-pause-circle', 'fa-play-circle'],\n actions: ['play', 'pause']\n });\n }\n\n if (this.params['mute-button']) {\n generateActionButton(this, {\n name: 'muted',\n className: 'mute-toggle',\n innerHtml: '',\n initialState: this.muted,\n stateClassName: 'muted',\n condition_parameter: 'muted',\n stateChildClassNames: ['fa-volume-up', 'fa-volume-mute'],\n actions: ['unmute', 'mute']\n });\n }\n }\n\n timeToPercentage(time) {\n if (time <= this.params['start-at']) return 0;\n if (time >= this.duration) return 100;\n if (time <= 0) return 0;\n time -= this.params['start-at']; // normalize\n const duration = this.duration - this.params['start-at']; // normalize\n return percentage(time, duration);\n }\n\n percentageToTime(percentage) {\n if (!this.duration) return this.params['start-at'] || 0;\n if (percentage > 100) return this.duration;\n if (percentage <= 0) return this.params['start-at'] || 0;\n const duration = this.duration - this.params['start-at']; // normalize\n let time = percentage * duration / 100;\n time = fixed(time, 3)\n if (time > duration) time = duration;\n if (this.params['start-at']) time += this.params['start-at']; // normalize\n return time;\n }\n\n resize(element) {\n if (!this.params['fit-box']) proportionalParentCoverResize(element || this.playerElement, this.params.resolution_mod, this.params.offset);\n this.dispatchEvent('video-background-resize');\n }\n\n stylePlayerElement(element) {\n if (!element) return;\n\n if (this.params['inline-styles']) {\n element.style.top = '50%';\n element.style.left = '50%';\n element.style.transform = 'translateX(-50%) translateY(-50%)';\n element.style.position = 'absolute';\n element.style.opacity = 0;\n }\n\n if (this.params['fit-box']) {\n element.style.width = '100%';\n element.style.height = '100%';\n }\n }\n\n buildWrapperHTML() {\n const parent = this.element.parentNode;\n // wrap\n this.element.classList.add('youtube-background', 'video-background');\n \n //set css rules\n const wrapper_styles = {\n \"height\" : \"100%\",\n \"width\" : \"100%\",\n \"z-index\": \"0\",\n \"position\": \"absolute\",\n \"overflow\": \"hidden\",\n \"top\": 0, // added by @insad\n \"left\": 0,\n \"bottom\": 0,\n \"right\": 0\n };\n \n if (!this.params['mute-button']) {\n wrapper_styles[\"pointer-events\"] = \"none\" // avoid right mouse click popup menu\n }\n \n if (this.params['load-background'] || this.params['poster']) {\n this.loadBackground(this.id);\n if (this.params['poster']) wrapper_styles['background-image'] = `url(${ this.params['poster'] })`;\n wrapper_styles['background-size'] = 'cover';\n wrapper_styles['background-repeat'] = 'no-repeat';\n wrapper_styles['background-position'] = 'center';\n }\n \n if (this.params['inline-styles']) {\n for (let property in wrapper_styles) {\n this.element.style[property] = wrapper_styles[property];\n }\n \n if (!['absolute', 'fixed', 'relative', 'sticky'].indexOf(parent.style.position)) {\n parent.style.position = 'relative';\n }\n }\n \n // set play/mute controls wrap\n if (this.params['play-button'] || this.params['mute-button']) {\n const controls = document.createElement('div');\n controls.className = 'video-background-controls';\n \n controls.style.position = 'absolute';\n controls.style.top = '10px';\n controls.style.right = '10px';\n controls.style['z-index'] = 2;\n \n this.controls_element = controls;\n parent.appendChild(controls);\n }\n \n return this.element;\n }\n\n loadBackground(id) {\n if (!this.params['load-background']) return;\n if (!id) return;\n if (this.type === 'youtube') this.element.style['background-image'] = `url(https://img.youtube.com/vi/${id}/hqdefault.jpg)`;\n if (this.type === 'vimeo') this.element.style['background-image'] = `url(https://vumbnail.com/${id}.jpg)`;\n }\n\n destroy() {\n this.playerElement.remove();\n this.element.classList.remove('youtube-background', 'video-background');\n this.element.removeAttribute('data-vbg-uid');\n this.element.style = '';\n\n if (this.params['play-button'] || this.params['mute-button']) {\n this.controls_element.remove();\n }\n\n if (this.timeUpdateTimer) clearInterval(this.timeUpdateTimer);\n this.dispatchEvent('video-background-destroyed');\n }\n\n setDuration(duration) {\n if (this.duration === duration) return;\n\n if (this.params['end-at']) {\n if (duration > this.params['end-at']) {\n this.duration = this.params['end-at'];\n return;\n }\n if (duration < this.params['end-at']) {\n this.duration = duration;\n return;\n }\n } else {\n this.duration = duration;\n return;\n }\n\n if (duration <= 0) this.duration = this.params['end-at'];\n }\n\n setStartAt(startAt) {\n this.params['start-at'] = startAt;\n }\n\n setEndAt(endAt) {\n this.params['end-at'] = endAt;\n if (this.duration > endAt) this.duration = endAt;\n if (this.currentTime > endAt) this.onVideoEnded();\n }\n\n dispatchEvent(name) {\n this.element.dispatchEvent(new CustomEvent(name, { bubbles: true, detail: this }));\n }\n\n shouldPlay() {\n if (this.currentState === 'ended' && !this.params.loop) return false;\n if (this.params['always-play'] && this.currentState !== 'playing') return true;\n if (this.isIntersecting && this.params.autoplay && this.currentState !== 'playing') return true;\n return false;\n }\n\n mobileLowBatteryAutoplayHack() {\n if (!this.params['force-on-low-battery']) return;\n if (!this.is_mobile && this.params.mobile) return;\n\n const forceAutoplay = function() {\n if (!this.initialPlay && this.params.autoplay && this.params.muted) {\n this.softPlay();\n\n if (!this.isIntersecting && !this.params['always-play']) {\n this.softPause();\n }\n }\n }\n \n document.addEventListener('touchstart', forceAutoplay.bind(this), { once: true });\n }\n\n parseProperties(params, defaults, element, attr_prefix) {\n let res_params = {};\n \n if (!params) {\n res_params = defaults;\n } else {\n for (let k in defaults) {\n //load in defaults if the param hasn't been set\n res_params[k] = !params.hasOwnProperty(k) ? defaults[k] : params[k];\n }\n }\n \n if (!element) return res_params;\n // load params from data attributes\n for (let k in res_params) {\n let data;\n \n if (isArray(attr_prefix)) {\n for (let i = 0; i < attr_prefix.length; i++) {\n const temp_data = element.getAttribute(attr_prefix[i]+k);\n if (temp_data) {\n data = temp_data;\n break;\n }\n }\n } else {\n data = element.getAttribute(attr_prefix+k);\n }\n \n if (data !== undefined && data !== null) {\n res_params[k] = stringToType(data);\n }\n }\n \n return res_params;\n }\n}\n", "import { SuperVideoBackground } from './super-video-background.js';\nimport { RE_YOUTUBE } from 'book-of-spells';\n\nexport class YoutubeBackground extends SuperVideoBackground {\n constructor(elem, params, id, uid, factoryInstance) {\n super(elem, params, id, uid, 'youtube', factoryInstance);\n\n if (!id) return;\n if (this.is_mobile && !this.params.mobile) return;\n this.injectScript();\n\n this.player = null;\n\n this.injectPlayer();\n\n this.STATES = {\n '-1': 'notstarted',\n '0': 'ended',\n '1': 'playing',\n '2': 'paused',\n '3': 'buffering',\n '5': 'cued'\n };\n\n this.timeUpdateTimer = null;\n this.timeUpdateInterval = 250;\n\n this.initYTPlayer();\n }\n\n startTimeUpdateTimer() {\n if (this.timeUpdateTimer) return;\n this.timeUpdateTimer = setInterval(this.onVideoTimeUpdate.bind(this), this.timeUpdateInterval);\n };\n\n stopTimeUpdateTimer() {\n clearInterval(this.timeUpdateTimer);\n this.timeUpdateTimer = null;\n };\n\n convertState(state) {\n return this.STATES[state];\n }\n\n initYTPlayer() {\n if (!window.hasOwnProperty('YT') || this.player !== null) return;\n\n this.player = new YT.Player(this.uid, {\n events: {\n 'onReady': this.onVideoPlayerReady.bind(this),\n 'onStateChange': this.onVideoStateChange.bind(this),\n // 'onError': this.onVideoError.bind(this)\n }\n });\n\n if (this.volume !== 1 && !this.muted) this.setVolume(this.volume);\n }\n\n onVideoError(event) {\n console.error(event);\n }\n\n injectScript() {\n const src = 'https://www.youtube.com/player_api';\n if (window.hasOwnProperty('YT') || document.querySelector(`script[src=\"${src}\"]`)) return\n const tag = document.createElement('script');\n tag.async = true;\n tag.src = src;\n const firstScriptTag = document.getElementsByTagName('script')[0];\n firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);\n }\n\n generatePlayerElement() {\n const playerElement = document.createElement('iframe');\n if (this.params.title) playerElement.setAttribute('title', this.params.title);\n playerElement.setAttribute('frameborder', 0);\n playerElement.setAttribute('allow', 'autoplay; mute');\n if (this.params['lazyloading']) playerElement.setAttribute('loading', 'lazy');\n\n return playerElement;\n }\n\n generateSrcURL(id) {\n let site = 'https://www.youtube.com/embed/';\n if (this.params['no-cookie']) {\n site = 'https://www.youtube-nocookie.com/embed/';\n }\n let src = `${site}${id}?&enablejsapi=1&disablekb=1&controls=0&rel=0&iv_load_policy=3&cc_load_policy=0&playsinline=1&showinfo=0&modestbranding=1&fs=0`;\n\n if (this.params.muted) {\n src += '&mute=1';\n }\n \n if (this.params.autoplay && (this.params['always-play'] || this.isIntersecting)) {\n src += '&autoplay=1';\n }\n \n if (this.params.loop) {\n src += '&loop=1';\n }\n\n return src;\n }\n\n injectPlayer() {\n this.playerElement = this.generatePlayerElement();\n this.src = this.generateSrcURL(this.id);\n this.playerElement.src = this.src;\n this.playerElement.id = this.uid;\n\n this.stylePlayerElement(this.playerElement);\n this.element.appendChild(this.playerElement);\n this.resize(this.playerElement);\n }\n\n /* ===== API ===== */\n\n setSource(url) {\n const pts = url.match(RE_YOUTUBE);\n if (!pts || !pts.length) return;\n\n this.id = pts[1];\n this.src = this.generateSrcURL(this.id);\n this.playerElement.src = this.src;\n\n if (this.element.hasAttribute('data-vbg')) this.element.setAttribute('data-vbg', this.src);\n if (this.element.hasAttribute('data-ytbg')) this.element.setAttribute('data-ytbg', this.src);\n this.loadBackground(this.id);\n }\n\n onVideoTimeUpdate() {\n const ctime = this.player.getCurrentTime();\n if (ctime === this.currentTime) return;\n this.currentTime = ctime;\n this.percentComplete = this.timeToPercentage(this.currentTime);\n if (this.params['end-at'] && this.duration && this.currentTime >= this.duration) {\n this.currentState = 'ended';\n this.dispatchEvent('video-background-state-change');\n this.onVideoEnded();\n this.stopTimeUpdateTimer();\n return;\n }\n this.dispatchEvent('video-background-time-update');\n }\n\n onVideoPlayerReady() {\n this.mobileLowBatteryAutoplayHack();\n\n if (this.params.autoplay && (this.params['always-play'] || this.isIntersecting)) {\n if (this.params['start-at']) this.seekTo(this.params['start-at']);\n this.player.playVideo();\n }\n\n this.setDuration(this.player.getDuration());\n\n this.dispatchEvent('video-background-ready');\n }\n\n onVideoStateChange(event) {\n this.currentState = this.convertState(event.data);\n\n if (this.currentState === 'ended') this.onVideoEnded();\n \n if (this.currentState === 'notstarted' && this.params.autoplay) {\n this.seekTo(this.params['start-at']);\n this.player.playVideo();\n }\n\n if (this.currentState === 'playing') this.onVideoPlay();\n \n if (this.currentState === 'paused') this.onVideoPause();\n\n this.dispatchEvent('video-background-state-change');\n }\n\n onVideoPlay() {\n if (!this.initialPlay) {\n this.initialPlay = true;\n this.playerElement.style.opacity = 1;\n }\n\n const seconds = this.player.getCurrentTime();\n if (this.params['start-at'] && seconds < this.params['start-at'] ) {\n this.seekTo(this.params['start-at']);\n }\n\n if (this.duration && seconds >= this.duration) {\n this.seekTo(this.params['start-at']);\n }\n\n if (!this.duration) {\n this.setDuration(this.player.getDuration());\n }\n\n this.dispatchEvent('video-background-play');\n this.startTimeUpdateTimer();\n }\n\n onVideoPause() {\n this.stopTimeUpdateTimer();\n this.dispatchEvent('video-background-pause');\n }\n\n onVideoEnded() {\n this.dispatchEvent('video-background-ended');\n\n if (!this.params.loop) return this.pause();\n this.seekTo(this.params['start-at']);\n this.player.playVideo();\n }\n\n seek(percentage) {\n this.seekTo(this.percentageToTime(percentage), true);\n }\n\n seekTo(seconds, allowSeekAhead = true) {\n if (!this.player) return;\n this.player.seekTo(seconds, allowSeekAhead);\n this.dispatchEvent('video-background-seeked');\n }\n\n softPause() {\n if (!this.player || this.currentState === 'paused') return;\n this.stopTimeUpdateTimer();\n this.player.pauseVideo();\n }\n\n softPlay() {\n if (!this.player || this.currentState === 'playing') return;\n this.player.playVideo();\n }\n\n play() {\n if (!this.player) return;\n this.paused = false;\n \n this.player.playVideo();\n }\n\n pause() {\n if (!this.player) return;\n this.paused = true;\n this.stopTimeUpdateTimer();\n this.player.pauseVideo();\n }\n\n unmute() {\n if (!this.player) return;\n this.muted = false;\n \n if (!this.initialVolume) {\n this.initialVolume = true;\n this.setVolume(this.params.volume);\n }\n this.player.unMute();\n this.dispatchEvent('video-background-unmute');\n }\n\n mute() {\n if (!this.player) return;\n this.muted = true;\n \n this.player.mute();\n this.dispatchEvent('video-background-mute');\n }\n\n getVolume() {\n if (!this.player) return;\n return this.player.getVolume() / 100;\n }\n\n setVolume(volume) {\n if (!this.player) return;\n this.volume = volume;\n \n this.player.setVolume(volume * 100);\n this.dispatchEvent('video-background-volume-change');\n }\n}\n ", "import { SuperVideoBackground } from './super-video-background.js';\nimport { RE_VIMEO } from 'book-of-spells';\n\nexport class VimeoBackground extends SuperVideoBackground {\n constructor(elem, params, id, uid, factoryInstance) {\n super(elem, params, id.id, uid, 'vimeo', factoryInstance);\n if (!id) return;\n this.unlisted = id.unlisted;\n\n if (this.is_mobile && !this.params.mobile) return;\n this.injectScript();\n\n this.player = null;\n\n this.injectPlayer();\n\n this.initVimeoPlayer();\n }\n\n injectScript() {\n const src = 'https://player.vimeo.com/api/player.js';\n if (window.hasOwnProperty('Vimeo') || document.querySelector(`script[src=\"${src}\"]`)) return;\n const tag = document.createElement('script');\n tag.async = true;\n if (window.hasOwnProperty('onVimeoIframeAPIReady') && typeof window.onVimeoIframeAPIReady === 'function') tag.addEventListener('load', () => {\n window.onVimeoIframeAPIReady();\n });\n tag.src = src;\n const firstScriptTag = document.getElementsByTagName('script')[0];\n firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);\n }\n\n initVimeoPlayer() {\n if (!window.hasOwnProperty('Vimeo') || this.player !== null) return;\n this.player = new Vimeo.Player(this.playerElement);\n \n this.player.on('loaded', this.onVideoPlayerReady.bind(this));\n this.player.on('ended', this.onVideoEnded.bind(this));\n this.player.on('play', this.onVideoPlay.bind(this));\n this.player.on('pause', this.onVideoPause.bind(this));\n this.player.on('bufferstart', this.onVideoBuffering.bind(this));\n this.player.on('timeupdate', this.onVideoTimeUpdate.bind(this));\n // this.player.on('error', this.onVideoError.bind(this));\n\n if (this.volume !== 1 && !this.muted) this.setVolume(this.volume);\n }\n\n onVideoError(event) {\n console.error(event);\n }\n\n generatePlayerElement() {\n const playerElement = document.createElement('iframe');\n if (this.params.title) playerElement.setAttribute('title', this.params.title);\n playerElement.setAttribute('frameborder', 0);\n playerElement.setAttribute('allow', 'autoplay; mute');\n if (this.params['lazyloading']) playerElement.setAttribute('loading', 'lazy');\n\n return playerElement;\n }\n\n generateSrcURL(id, unlisted) {\n unlisted = unlisted ? `h=${unlisted}&` : ''\n let src = `https://player.vimeo.com/video/${id}?${unlisted}background=1&controls=0`;\n \n if (this.params.muted) {\n src += '&muted=1';\n }\n \n if (this.params.autoplay && (this.params['always-play'] || this.isIntersecting)) {\n src += '&autoplay=1';\n }\n\n if (this.params.loop) {\n src += '&loop=1&autopause=0';\n }\n \n if (this.params['no-cookie']) {\n src += '&dnt=1';\n }\n \n //WARN\u2757\uFE0F: this is a hash not a query param\n if (this.params['start-at']) {\n src += '#t=' + this.params['start-at'] + 's';\n }\n\n return src;\n }\n\n injectPlayer() {\n this.playerElement = this.generatePlayerElement();\n this.src = this.generateSrcURL(this.id, this.unlisted);\n this.playerElement.src = this.src;\n this.playerElement.id = this.uid;\n \n this.stylePlayerElement(this.playerElement);\n this.element.appendChild(this.playerElement);\n this.resize(this.playerElement);\n }\n\n updateState(state) {\n this.currentState = state;\n this.dispatchEvent('video-background-state-change');\n }\n\n /* ===== API ===== */\n\n setSource(url) {\n const pts = url.match(RE_VIMEO);\n if (!pts || !pts.length) return;\n\n this.id = pts[1];\n this.src = this.generateSrcURL(this.id);\n this.playerElement.src = this.src;\n\n if (this.element.hasAttribute('data-vbg')) this.element.setAttribute('data-vbg', this.src);\n if (this.element.hasAttribute('data-ytbg')) this.element.setAttribute('data-ytbg', this.src);\n this.loadBackground(this.id);\n }\n\n onVideoPlayerReady() {\n this.mobileLowBatteryAutoplayHack();\n\n if (this.params['start-at']) this.seekTo(this.params['start-at']);\n\n if (this.params.autoplay && (this.params['always-play'] || this.isIntersecting)) {\n this.player.play();\n }\n\n this.player.getDuration().then((duration) => {\n this.setDuration(duration);\n });\n\n this.dispatchEvent('video-background-ready');\n }\n\n onVideoEnded() {\n this.updateState('ended');\n this.dispatchEvent('video-background-ended');\n if (!this.params.loop) return this.pause();\n \n this.seekTo(this.params['start-at']);\n this.updateState('playing');\n this.dispatchEvent('video-background-play');\n }\n\n onVideoTimeUpdate(event) {\n this.currentTime = event.seconds;\n this.percentComplete = this.timeToPercentage(event.seconds);\n this.dispatchEvent('video-background-time-update');\n this.setDuration(event.duration);\n\n if (this.params['end-at'] && this.duration && event.seconds >= this.duration) {\n this.onVideoEnded();\n }\n }\n\n onVideoBuffering() {\n this.updateState('buffering');\n }\n\n onVideoPlay(event) {\n this.setDuration(event.duration);\n\n if (!this.initialPlay) {\n this.initialPlay = true;\n this.playerElement.style.opacity = 1;\n\n // gotta set loop manually, cause for some reason it's true by default\n this.player.setLoop(this.params.loop);\n\n //Hotfixing an issue that it automatically starts playing after buffering on the first load, sometimes, not always, for an unknown reason\n if (!(this.params.autoplay && (this.params['always-play'] || this.isIntersecting))) {\n return this.player.pause();\n }\n }\n\n const seconds = event.seconds;\n if (this.params['start-at'] && seconds < this.params['start-at']) {\n this.seekTo(this.params['start-at']);\n }\n\n if (this.duration && seconds >= this.duration) {\n this.seekTo(this.params['start-at']);\n }\n\n this.updateState('playing');\n this.dispatchEvent('video-background-play');\n }\n\n onVideoPause() {\n this.updateState('paused');\n this.dispatchEvent('video-background-pause');\n }\n\n seek(percentage) {\n this.seekTo(this.percentageToTime(percentage));\n }\n\n seekTo(time) {\n if (!this.player) return;\n this.player.setCurrentTime(time);\n this.dispatchEvent('video-background-seeked');\n }\n\n softPause() {\n if (!this.player || this.currentState === 'paused') return;\n this.player.pause();\n }\n\n softPlay() {\n if (!this.player || this.currentState === 'playing') return;\n this.player.play();\n }\n\n play() {\n if (!this.player) return;\n this.paused = false;\n \n this.player.play();\n }\n\n pause() {\n if (!this.player) return;\n this.paused = true;\n \n this.player.pause();\n }\n\n unmute() {\n if (!this.player) return;\n this.muted = false;\n \n if (!this.initialVolume) {\n this.initialVolume = true;\n this.setVolume(this.params.volume);\n }\n this.player.setMuted(false);\n this.dispatchEvent('video-background-unmute');\n }\n\n mute() {\n if (!this.player) return;\n this.muted = true;\n \n this.player.setMuted(true);\n this.dispatchEvent('video-background-mute');\n }\n\n getVolume() {\n if (!this.player) return;\n return this.player.getVolume();\n }\n\n setVolume(volume) {\n if (!this.player) return;\n this.volume = volume;\n \n this.player.setVolume(volume);\n this.dispatchEvent('video-background-volume-change');\n }\n}\n", "import { SuperVideoBackground } from './super-video-background.js';\nimport { RE_VIDEO } from 'book-of-spells';\n\nexport class VideoBackground extends SuperVideoBackground {\n constructor(elem, params, vid_data, uid, factoryInstance) {\n super(elem, params, vid_data.link, uid, 'video', factoryInstance);\n if (!vid_data || !vid_data.link) return;\n if (this.is_mobile && !this.params.mobile) return;\n\n this.src = vid_data.link;\n this.ext = /(?:\\.([^.]+))?$/.exec(vid_data.id)[1];\n this.uid = uid;\n this.element.setAttribute('data-vbg-uid', uid);\n this.player = null;\n this.buttons = {};\n\n this.MIME_MAP = {\n 'ogv' : 'video/ogg',\n 'ogm' : 'video/ogg',\n 'ogg' : 'video/ogg',\n 'avi' : 'video/avi',\n 'mp4' : 'video/mp4',\n 'webm' : 'video/webm',\n 'm4v' : 'video/x-m4v',\n 'mov' : 'video/quicktime',\n 'qt' : 'video/quicktime',\n };\n\n this.mime = this.MIME_MAP[this.ext.toLowerCase()];\n\n this.injectPlayer();\n\n this.mobileLowBatteryAutoplayHack();\n this.dispatchEvent('video-background-ready');\n }\n\n generatePlayerElement() {\n const playerElement = document.createElement('video');\n if (this.params.title) playerElement.setAttribute('title', this.params.title);\n playerElement.setAttribute('playsinline', '');\n if (this.params.loop) playerElement.setAttribute('loop', '');\n if (this.params.autoplay && (this.params['always-play'] || this.isIntersecting)) {\n playerElement.setAttribute('autoplay', '');\n playerElement.autoplay = true;\n }\n if (this.muted) {\n playerElement.setAttribute('muted', '');\n playerElement.muted = true;\n }\n if (this.params['lazyloading']) playerElement.setAttribute('loading', 'lazy');\n\n return playerElement;\n }\n\n injectPlayer() {\n this.player = this.generatePlayerElement();\n this.playerElement = this.player;\n \n if (this.volume !== 1 && !this.muted) this.setVolume(this.volume);\n \n this.playerElement.setAttribute('id', this.uid)\n \n this.stylePlayerElement(this.playerElement);\n\n this.player.addEventListener('loadedmetadata', this.onVideoLoadedMetadata.bind(this));\n this.player.addEventListener('durationchange', this.onVideoLoadedMetadata.bind(this));\n this.player.addEventListener('canplay', this.onVideoCanPlay.bind(this));\n this.player.addEventListener('timeupdate', this.onVideoTimeUpdate.bind(this));\n this.player.addEventListener('play', this.onVideoPlay.bind(this));\n this.player.addEventListener('pause', this.onVideoPause.bind(this));\n this.player.addEventListener('waiting', this.onVideoBuffering.bind(this));\n this.player.addEventListener('ended', this.onVideoEnded.bind(this));\n\n this.element.appendChild(this.playerElement);\n const source = document.createElement('source');\n source.setAttribute('src', this.src);\n source.setAttribute('type', this.mime);\n this.playerElement.appendChild(source);\n this.resize(this.playerElement);\n }\n\n updateState(state) {\n this.currentState = state;\n this.dispatchEvent('video-background-state-change');\n }\n\n /* ===== API ===== */\n\n setSource(url) {\n const pts = url.match(RE_VIDEO);\n if (!pts || !pts.length) return;\n this.id = pts[1];\n this.ext = /(?:\\.([^.]+))?$/.exec(this.id)[1];\n this.mime = this.MIME_MAP[this.ext.toLowerCase()];\n this.playerElement.innerHTML = '';\n const source = document.createElement('source');\n source.setAttribute('src', url);\n source.setAttribute('type', this.mime);\n this.playerElement.appendChild(source);\n this.src = url;\n\n if (this.element.hasAttribute('data-vbg')) this.element.setAttribute('data-vbg', this.src);\n if (this.element.hasAttribute('data-ytbg')) this.element.setAttribute('data-ytbg', this.src);\n }\n\n onVideoLoadedMetadata() {\n this.setDuration(this.player.duration);\n }\n\n onVideoCanPlay() {\n this.setDuration(this.player.duration);\n }\n\n onVideoTimeUpdate() {\n this.currentTime = this.player.currentTime;\n this.percentComplete = this.timeToPercentage(this.player.currentTime);\n this.dispatchEvent('video-background-time-update');\n\n if (this.params['end-at'] && this.currentTime >= this.duration) {\n this.onVideoEnded();\n }\n }\n\n onVideoPlay() {\n if (!this.initialPlay) {\n this.initialPlay = true;\n this.playerElement.style.opacity = 1;\n }\n \n const seconds = this.player.currentTime;\n if (this.params['start-at'] && seconds <= this.params['start-at']) {\n this.seekTo(this.params['start-at']);\n }\n\n if (this.duration && seconds >= this.duration) {\n this.seekTo(this.params['start-at']);\n }\n\n this.updateState('playing');\n this.dispatchEvent('video-background-play');\n }\n\n onVideoPause() {\n this.updateState('paused');\n this.dispatchEvent('video-background-pause');\n }\n\n onVideoEnded() {\n this.updateState('ended');\n this.dispatchEvent('video-background-ended');\n if (!this.params.loop) return this.pause();\n \n this.seekTo(this.params['start-at']);\n this.onVideoPlay();\n }\n\n onVideoBuffering() {\n this.updateState('buffering');\n }\n\n seek(percentage) {\n this.seekTo(this.percentageToTime(percentage));\n }\n\n seekTo(seconds) {\n if (!this.player) return;\n if (this.player.hasOwnProperty('fastSeek')) {\n this.player.fastSeek(seconds);\n return;\n }\n this.player.currentTime = seconds;\n this.dispatchEvent('video-background-seeked');\n }\n\n softPause() {\n if (!this.player || this.currentState === 'paused') return;\n this.player.pause();\n }\n\n softPlay() {\n if (!this.player || this.currentState === 'playing') return;\n this.player.play();\n }\n\n play() {\n if (!this.player) return;\n this.paused = false;\n\n this.player.play();\n }\n\n pause() {\n if (!this.player) return;\n this.paused = true;\n \n this.player.pause();\n }\n\n unmute() {\n if (!this.player) return;\n this.muted = false;\n \n this.player.muted = false;\n if (!this.initialVolume) {\n this.initialVolume = true;\n this.setVolume(this.params.volume);\n }\n this.dispatchEvent('video-background-unmute');\n }\n\n mute() {\n if (!this.player) return;\n this.muted = true;\n \n this.player.muted = true;\n this.dispatchEvent('video-background-mute');\n }\n\n getVolume() {\n if (!this.player) return;\n return this.player.volume;\n }\n\n setVolume(volume) {\n if (!this.player) return;\n this.volume = volume;\n \n this.player.volume = volume;\n this.dispatchEvent('video-background-volume-change');\n }\n}\n", "import { YoutubeBackground } from './lib/youtube-background.js';\nimport { VimeoBackground } from './lib/vimeo-background.js';\nimport { VideoBackground } from './lib/video-background.js';\n\nimport { randomIntInclusive, RE_VIMEO, RE_YOUTUBE, RE_VIDEO } from 'book-of-spells';\n\nexport class VideoBackgrounds {\n constructor(selector, params) {\n this.elements = selector;\n if (this.elements instanceof Element) this.elements = [this.elements];\n if (typeof this.elements === 'string') this.elements = document.querySelectorAll(selector);\n\n this.index = {};\n\n const self = this;\n\n this.intersectionObserver = null;\n\n if ('IntersectionObserver' in window) {\n this.intersectionObserver = new IntersectionObserver(function (entries) {\n entries.forEach(function (entry) {\n const uid = entry.target.getAttribute('data-vbg-uid');\n \n if (uid && self.index.hasOwnProperty(uid) && entry.isIntersecting) {\n self.index[uid].isIntersecting = true;\n try {\n if (self.index[uid].player && !self.index[uid].paused) self.index[uid].softPlay();\n } catch (e) {\n // console.log(e);\n }\n } else {\n self.index[uid].isIntersecting = false;\n try {\n if (self.index[uid].player) self.index[uid].softPause();\n } catch (e) {\n // console.log(e);\n }\n }\n });\n });\n }\n\n this.resizeObserver = null;\n\n if ('ResizeObserver' in window) {\n this.resizeObserver = new ResizeObserver(function (entries) {\n entries.forEach(function (entry) {\n const uid = entry.target.getAttribute('data-vbg-uid');\n\n if (uid && self.index.hasOwnProperty(uid)) {\n window.requestAnimationFrame(() => self.index[uid].resize());\n }\n });\n });\n } else {\n window.addEventListener('resize', function () {\n for (let k in self.index) {\n window.requestAnimationFrame(() => self.index[k].resize(self.index[k].playerElement));\n }\n });\n }\n \n this.initPlayers();\n\n if (!this.elements || !this.elements.length) return;\n for (let i = 0; i < this.elements.length; i++) {\n const element = this.elements[i];\n this.add(element, params);\n }\n\n document.addEventListener('visibilitychange', this.onVisibilityChange.bind(this));\n }\n\n onVisibilityChange() {\n if (document.hidden) return;\n\n for (let k in this.index) {\n const instance = this.index[k];\n if (instance.shouldPlay()) {\n instance.softPlay();\n }\n }\n }\n\n add(element, params) {\n if (!element) return;\n if (element.hasAttribute('data-vbg-uid')) return;\n\n if (!this.intersectionObserver) {\n if (!params) params = {};\n params['always-play'] = true;\n }\n\n const link = element.getAttribute('data-youtube') || element.getAttribute('data-vbg');\n const vid_data = this.getVidID(link);\n \n if (!vid_data) return;\n \n const uid = this.generateUID(vid_data.id);\n \n if (!uid) return;\n \n switch (vid_data.type) {\n case 'YOUTUBE':\n const yb = new YoutubeBackground(element, params, vid_data.id, uid, this);\n this.index[uid] = yb;\n break;\n case 'VIMEO':\n const vm = new VimeoBackground(element, params, vid_data, uid, this);\n this.index[uid] = vm;\n break;\n case 'VIDEO':\n const vid = new VideoBackground(element, params, vid_data, uid, this);\n this.index[uid] = vid;\n break;\n }\n\n if (this.resizeObserver) {\n this.resizeObserver.observe(element);\n }\n \n if (!this.index[uid].params['always-play'] && this.intersectionObserver) {\n this.intersectionObserver.observe(element);\n }\n }\n\n destroy(element) {\n const uid = element.uid || element.getAttribute('data-vbg-uid');\n if (uid && this.index.hasOwnProperty(uid)) {\n if (!this.index[uid].params['always-play'] && this.intersectionObserver) this.intersectionObserver.unobserve(element);\n if (this.resizeObserver) this.resizeObserver.unobserve(element);\n this.index[uid].destroy();\n delete this.index[uid];\n }\n }\n\n destroyAll() {\n for (let k in this.index) {\n this.destroy(this.index[k].playerElement);\n }\n }\n\n getVidID(link) {\n if (link === undefined && link === null) return;\n\n this.re = {};\n this.re.YOUTUBE = RE_YOUTUBE;\n this.re.VIMEO = RE_VIMEO;\n this.re.VIDEO = RE_VIDEO;\n \n for (let k in this.re) {\n const pts = link.match(this.re[k]);\n\n if (pts && pts.length) {\n this.re[k].lastIndex = 0;\n const data = {\n id: pts[1],\n type: k,\n regex_pts: pts,\n link: link\n };\n \n if (k === 'VIMEO') {\n const unlistedQueryRegex = /(\\?|&)h=([^=&#?]+)/;\n const unlistedPathRegex = /\\/[^\\/\\:\\.]+(\\:|\\/)([^:?\\/]+)\\s?$/;\n const unlistedQuery = link.match(unlistedPathRegex) || link.match(unlistedQueryRegex);\n if (unlistedQuery) data.unlisted = unlistedQuery[2];\n }\n\n return data;\n }\n }\n \n return;\n }\n\n generateUID(pref) {\n //index the instance\n pref = pref.replace(/[^a-zA-Z0-9\\-_]/g, '-'); //sanitize id\n pref = pref.replace(/-{2,}/g, '-'); //remove double dashes\n pref = pref.replace(/^-+/, '').replace(/-+$/, ''); //trim dashes\n pref = 'vbg-'+ pref; //prefix id with 'vbg-\n\n let uid = pref +'-'+ randomIntInclusive(0, 9999);\n while (this.index.hasOwnProperty(uid)) {\n uid = pref +'-'+ randomIntInclusive(0, 9999);\n }\n \n return uid;\n }\n\n get(element) {\n const uid = typeof element === 'string' ? element : element.getAttribute('data-vbg-uid');\n if (uid && this.index.hasOwnProperty(uid)) return this.index[uid];\n }\n\n pauseAll() {\n for (let k in this.index) {\n this.index[k].pause();\n }\n }\n\n playAll() {\n for (let k in this.index) {\n this.index[k].play();\n }\n }\n\n muteAll() {\n for (let k in this.index) {\n this.index[k].mute();\n }\n }\n\n unmuteAll() {\n for (let k in this.index) {\n this.index[k].unmute();\n }\n }\n\n setVolumeAll(volume) {\n for (let k in this.index) {\n this.index[k].setVolume(volume);\n }\n }\n\n initPlayers(callback) {\n const self = this;\n \n window.onYouTubeIframeAPIReady = function () {\n for (let k in self.index) {\n if (self.index[k] instanceof YoutubeBackground) {\n self.index[k].initYTPlayer();\n }\n }\n \n if (callback) {\n setTimeout(callback, 100);\n }\n };\n \n if (window.hasOwnProperty('YT') && window.YT.loaded) {\n window.onYouTubeIframeAPIReady();\n }\n \n window.onVimeoIframeAPIReady = function () {\n for (let k in self.index) {\n if (self.index[k] instanceof VimeoBackground) {\n self.index[k].initVimeoPlayer();\n }\n }\n \n if (callback) {\n setTimeout(callback, 100);\n }\n }\n \n if (window.hasOwnProperty('Vimeo') && window.Vimeo.hasOwnProperty('Player')) {\n window.onVimeoIframeAPIReady();\n }\n }\n}\n", "import { VideoBackgrounds } from './video-backgrounds.js';\n\nif (typeof jQuery == 'function') {\n (function ($) {\n $.fn.youtube_background = function (params) {\n const $this = $(this);\n if (window.hasOwnProperty('VIDEO_BACKGROUNDS')) {\n window.VIDEO_BACKGROUNDS.add($this, params);\n return $this;\n }\n window.VIDEO_BACKGROUNDS = new VideoBackgrounds(this, params);\n return $this;\n };\n })(jQuery);\n}\n\nwindow.VideoBackgrounds = VideoBackgrounds;\n"], ++ "sourcesContent": ["\nfunction buttonOn(buttonObj) {\n if (!buttonObj) return;\n buttonObj.element.classList.add(buttonObj.stateClassName);\n buttonObj.element.firstChild.classList.remove(buttonObj.stateChildClassNames[0]);\n buttonObj.element.firstChild.classList.add(buttonObj.stateChildClassNames[1]);\n buttonObj.element.setAttribute('aria-checked', false);\n}\n\nfunction buttonOff(buttonObj) {\n if (!buttonObj) return;\n buttonObj.element.classList.remove(buttonObj.stateClassName);\n buttonObj.element.firstChild.classList.add(buttonObj.stateChildClassNames[0]);\n buttonObj.element.firstChild.classList.remove(buttonObj.stateChildClassNames[1]);\n buttonObj.element.setAttribute('aria-checked', true);\n}\n\nexport function generateActionButton(obj, props) {\n const btn = document.createElement('button');\n btn.className = props.className;\n btn.innerHTML = props.innerHtml;\n btn.setAttribute('role', 'switch');\n btn.firstChild.classList.add(props.stateChildClassNames[0]);\n btn.setAttribute('aria-checked', !props.initialState);\n props.element = btn;\n\n if (obj.params[props.condition_parameter] === props.initialState) {\n buttonOn(props);\n }\n\n btn.addEventListener('click', function(e) {\n if (this.classList.contains(props.stateClassName)) {\n buttonOff(props);\n obj[props.actions[0]]();\n } else {\n buttonOn(props);\n obj[props.actions[1]]();\n }\n });\n\n obj.buttons[props.name] = {\n element: btn,\n button_properties: props\n };\n\n obj.controls_element.appendChild(btn);\n};\n", "/**\n * Shallow merges two objects together. Used to pass simple options to functions.\n * \n * @param {object} target The target object to merge into\n * @param {object} source The source object to merge from\n * @returns object The merged object, in this case the target object with the source object's properties merged into it\n * @example\n * const target = { foo: 'bar' }\n * const source = { bar: 'baz' }\n * shallowMerge(target, source) // { foo: 'bar', bar: 'baz' }\n */\nexport function shallowMerge(target, source) {\n for (const key in source) {\n target[key] = source[key]\n }\n\n return target\n}\n\n/**\n * Deep merge function that's mindful of arrays and objects\n * \n * @param {object} target The target object to merge into\n * @param {object} source The source object to merge from\n * @returns object The merged object, in this case the target object with the source object's properties merged into it\n * @example\n * const target = { foo: 'bar' }\n * const source = { bar: 'baz' }\n * deepMerge(target, source) // { foo: 'bar', bar: 'baz' }\n */\nexport function deepMerge(target, source) {\n if (isObject(source) && isObject(target)) {\n for (const key in source) {\n target[key] = deepMerge(target[key], source[key])\n }\n } else if (isArray(source) && isArray(target)) {\n for (let i = 0; i < source.length; i++) {\n target[i] = deepMerge(target[i], source[i])\n }\n } else {\n target = source\n }\n return target\n}\n\n/**\n * Deep clone function that's mindful of nested arrays and objects\n * \n * @param {object} o The object to clone\n * @returns object The cloned object\n * @example\n * const obj = { foo: 'bar' }\n * const clone = clone(obj)\n * clone.foo = 'baz'\n * console.log(obj.foo) // 'bar'\n * console.log(clone.foo) // 'baz'\n * console.log(obj === clone) // false\n * console.log(JSON.stringify(obj) === JSON.stringify(clone)) // true\n * @todo Check if faster than assign. This function is pretty old...\n */ \nexport function clone(o) {\n let res = null\n if (isArray(o)) {\n res = []\n for (const i in o) {\n res[i] = clone(o[i])\n }\n } else if (isObject(o)) {\n res = {}\n for (const i in o) {\n res[i] = clone(o[i])\n }\n } else {\n res = o\n }\n return res\n}\n\n/**\n * Check if an object is empty\n * \n * @param {object} o The object to check\n * @returns boolean True if the object is empty, false otherwise\n * @example\n * isEmptyObject({}) // => true\n * isEmptyObject({ foo: 'bar' }) // => false\n */\nexport function isEmptyObject(o) {\n for (const i in o) {\n return false\n }\n return true\n}\n\n/**\n * Check if an array is empty, substitute for Array.length === 0\n * \n * @param {array} o The array to check\n * @returns boolean True if the array is empty, false otherwise\n * @example\n * isEmptyArray([]) // => true\n * isEmptyArray([1, 2, 3]) // => false\n */\nexport function isEmptyArray(o) {\n return o.length === 0\n}\n\n/**\n * Check if a variable is empty\n * \n * @param {any} o The variable to check\n * @returns boolean True if the variable is empty, false otherwise\n * @example\n * isEmpty({}) // => true\n * isEmpty([]) // => true\n * isEmpty('') // => true\n * isEmpty(null) // => false\n * isEmpty(undefined) // => false\n * isEmpty(0) // => false\n */\nexport function isEmpty(o) {\n if (isObject(o)) {\n return isEmptyObject(o)\n } else if (isArray(o)) {\n return isEmptyArray(o)\n } else if (isString(o)) {\n return o === ''\n }\n return false\n}\n\n/**\n * Try to convert a string to a boolean\n * \n * @param {string} str The string to convert\n * @returns boolean The converted boolean or undefined if conversion failed\n * @example\n * stringToBoolean('true') // => true\n * stringToBoolean('false') // => false\n * stringToBoolean('foo') // => null\n */\nexport function stringToBoolean(str) {\n if (/^\\s*(true|false)\\s*$/i.test(str)) return str === 'true'\n}\n\n/**\n * Try to convert a string to a number\n * \n * @param {string} str The string to convert\n * @returns number The converted number or undefined if conversion failed\n * @example\n * stringToNumber('1') // => 1\n * stringToNumber('1.5') // => 1.5\n * stringToNumber('foo') // => null\n * stringToNumber('1foo') // => null\n */\nexport function stringToNumber(str) {\n if (/^\\s*\\d+\\s*$/.test(str)) return parseInt(str)\n if (/^\\s*[\\d.]+\\s*$/.test(str)) return parseFloat(str)\n}\n\n/**\n * Try to convert a string to an array\n * \n * @param {string} str The string to convert\n * @returns array The converted array or undefined if conversion failed\n * @example\n * stringToArray('[1, 2, 3]') // => [1, 2, 3]\n * stringToArray('foo') // => null\n * stringToArray('1') // => null\n * stringToArray('{\"foo\": \"bar\"}') // => null\n */\nexport function stringToArray(str) {\n if (!/^\\s*\\[.*\\]\\s*$/.test(str)) return\n try {\n return JSON.parse(str)\n } catch (e) {}\n}\n\n/**\n * Try to convert a string to an object\n * \n * @param {string} str The string to convert\n * @returns object The converted object or undefined if conversion failed\n * @example\n * stringToObject('{ \"foo\": \"bar\" }') // => { foo: 'bar' }\n * stringToObject('foo') // => null\n * stringToObject('1') // => null\n * stringToObject('[1, 2, 3]') // => null\n */\nexport function stringToObject(str) {\n if (!/^\\s*\\{.*\\}\\s*$/.test(str)) return\n try {\n return JSON.parse(str)\n } catch (e) {}\n}\n\n/**\n * Try to convert a string to a regex\n * \n * @param {string} str The string to convert\n * @returns regex The converted regex or undefined if conversion failed\n * @example\n * stringToRegex('/foo/i') // => /foo/i\n * stringToRegex('foo') // => null\n * stringToRegex('1') // => null\n */\nexport function stringToRegex(str) {\n if (!/^\\s*\\/.*\\/g?i?\\s*$/.test(str)) return\n try {\n return new RegExp(str)\n } catch (e) {}\n}\n\n/**\n * Try to convert a string to a primitive\n * \n * @param {string} str The string to convert\n * @returns {null|boolean|int|float|string} The converted primitive or input string if conversion failed\n * @example\n * stringToPrimitive('null') // => null\n * stringToPrimitive('true') // => true\n * stringToPrimitive('false') // => false\n * stringToPrimitive('1') // => 1\n * stringToPrimitive('1.5') // => 1.5\n * stringToPrimitive('foo') // => 'foo'\n * stringToPrimitive('1foo') // => '1foo'\n */\nexport function stringToPrimitive(str) {\n if (/^\\s*null\\s*$/.test(str)) return null\n const bool = stringToBoolean(str)\n if (bool !== undefined) return bool\n return stringToNumber(str) || str\n}\n\n/**\n * Try to convert a string to a data type\n * \n * @param {string} str The string to convert\n * @returns any The converted data type or input string if conversion failed\n * @example\n * stringToData('null') // => null\n * stringToData('true') // => true\n * stringToData('false') // => false\n * stringToData('1') // => 1\n * stringToData('1.5') // => 1.5\n * stringToData('foo') // => 'foo'\n * stringToData('1foo') // => '1foo'\n * stringToData('[1, 2, 3]') // => [1, 2, 3]\n * stringToData('{ \"foo\": \"bar\" }') // => { foo: 'bar' }\n * stringToData('/foo/i') // => /foo/i\n */\nexport function stringToType(str) {\n if (/^\\s*null\\s*$/.test(str)) return null\n const bool = stringToBoolean(str)\n if (bool !== undefined) return bool\n return stringToNumber(str) || stringToArray(str) || stringToObject(str) || stringToRegex(str) || str\n}\n\n/**\n * If provided variable is an object\n * \n * @param {any} o \n * @returns boolean\n * @example\n * isObject({}) // => true\n * isObject([]) // => false\n * isObject(null) // => false\n */\nexport function isObject(o) {\n return typeof o === 'object' && !Array.isArray(o) && o !== null\n}\n\n/**\n * If provided variable is an array. Just a wrapper for Array.isArray\n * \n * @param {any} o\n * @returns boolean\n * @example\n * isArray([]) // => true\n * isArray({}) // => false\n */\nexport function isArray(o) {\n return Array.isArray(o)\n}\n\n/**\n * If provided variable is a string. Just a wrapper for typeof === 'string'\n * \n * @param {any} o\n * @returns boolean\n * @example\n * isString('foo') // => true\n * isString({}) // => false\n */\nexport function isString(o) {\n return typeof o === 'string'\n}\n\n/**\n * If provided variable is a function, substitute for typeof === 'function'\n * \n * @param {any} o\n * @returns boolean\n * @example\n * isFunction(function() {}) // => true\n * isFunction({}) // => false\n */\nexport function isFunction(o) {\n return typeof o === 'function'\n}\n\n/**\n * If object property is a function\n * \n * @param {object} obj\n * @param {string} propertyName\n * @returns boolean\n * @example\n * const obj = { foo: 'bar', baz: function() {} }\n * propertyIsFunction(obj, 'foo') // => false\n * propertyIsFunction(obj, 'baz') // => true\n */\nexport function propertyIsFunction(obj, propertyName) {\n return obj.hasOwnProperty(propertyName) && isFunction(obj[propertyName])\n}\n\n/**\n * If object property is a string\n * \n * @param {object} obj\n * @param {string} propertyName\n * @returns boolean\n * @example\n * const obj = { foo: 'bar', baz: function() {} }\n * propertyIsString(obj, 'foo') // => true\n * propertyIsString(obj, 'baz') // => false\n */\nexport function propertyIsString(obj, propertyName) {\n return obj.hasOwnProperty(propertyName) && isString(obj[propertyName])\n}\n\n/**\n * Transforms a dash separated string to camelCase\n *\n * @param {string} str\n * @returns boolean\n * @example\n * transformDashToCamelCase('foo-bar') // => 'fooBar'\n * transformDashToCamelCase('foo-bar-baz') // => 'fooBarBaz'\n * transformDashToCamelCase('foo') // => 'foo'\n * transformDashToCamelCase('fooBarBaz-qux') // => 'fooBarBazQux'\n */\nexport function transformDashToCamelCase(str) {\n return str.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase() });\n}\n\n/**\n * Transforms a camelCase string to dash separated string\n * \n * @param {string} str\n * @returns boolean\n * @example\n * transformCamelCaseToDash('fooBar') // => 'foo-bar'\n * transformCamelCaseToDash('fooBarBaz') // => 'foo-bar-baz'\n * transformCamelCaseToDash('foo') // => 'foo'\n * transformDashToCamelCase('fooBarBaz-qux') // => 'foo-bar-baz-qux'\n */\nexport function transformCamelCaseToDash(str) {\n return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()\n}\n\n/**\n * Maps an array of objects by a property name\n * \n * @param {array} arr\n * @param {string} propertyName\n * @returns object\n * @example\n * const arr = [{ foo: 'bar' }, { foo: 'baz' }]\n * mapByProperty(arr, 'foo') // => { bar: { foo: 'bar' }, baz: { foo: 'baz' } }\n */\nexport function mapByProperty(arr, propertyName) {\n const res = {}\n for (let i = 0; i < arr.length; i++) {\n res[arr[i][propertyName]] = arr[i]\n }\n return res\n}\n\n/**\n * Maps an array of objects by a property name to another property name\n * \n * @param {array} arr\n * @param {string} keyPropertyName\n * @param {string} valuePropertyName\n * @returns object\n * @example\n * const arr = [{ foo: 'bar', baz: 'qux' }, { foo: 'quux', baz: 'corge' }]\n * mapPropertyToProperty(arr, 'foo', 'baz') // => { bar: 'qux', quux: 'corge' }\n */\nexport function mapPropertyToProperty(arr, keyPropertyName, valuePropertyName) {\n const res = {}\n for (let i = 0; i < arr.length; i++) {\n res[arr[i][keyPropertyName]] = arr[i][valuePropertyName]\n }\n return res\n}\n\n/**\n * Remove accents from a string\n * \n * @param {string} inputString\n * @returns string\n * @example\n * removeAccents('\u00E1\u00E9\u00ED\u00F3\u00FA') // => 'aeiou'\n * removeAccents('\u00C1\u00C9\u00CD\u00D3\u00DA') // => 'AEIOU'\n * removeAccents('se\u00F1or') // => 'senor'\n * removeAccents('\u0152') // => 'OE'\n */\nexport function removeAccents(inputString) {\n return inputString.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').replace(/\\\u0153/g, \"oe\").replace(/\\\u00E6/g, \"ae\").normalize('NFC')\n}\n\n/**\n * Strip HTML tags from a string\n * \n * @param {string} inputString\n * @returns string\n * @example\n * stripHTMLTags('foo') // => 'foo'\n * stripHTMLTags('foo bar') // => 'foo bar'\n */\nexport function stripHTMLTags(inputString) {\n return inputString.replace(/<[^>]*>/g, '')\n}\n\n/**\n * Slugify a string, e.g. 'Foo Bar' => 'foo-bar'. Similar to WordPress' sanitize_title(). Will remove accents and HTML tags.\n * \n * @param {string} str \n * @returns string\n * @example\n * slugify('Foo Bar') // => 'foo-bar'\n * slugify('Foo Bar baz') // => 'foo-bar-baz'\n */\nexport function slugify(str) {\n str = str.trim().toLowerCase()\n str = removeAccents(str)\n str = stripHTMLTags(str)\n return str.replace(/\\s+|\\.+|\\/+|\\\\+|\u2014+|\u2013+/g, '-').replace(/[^\\w0-9\\-]+/g, '').replace(/-{2,}/g, '-').replace(/^-|-$/g, '')\n}\n\n/**\n * Check if object has multiple properties\n * \n * @param {object} obj\n * @param {string|array} properties\n * @returns boolean\n * @example\n * const obj = { foo: 'bar', baz: 'qux' }\n * hasOwnProperties(obj, ['foo', 'baz']) // => true\n * hasOwnProperties(obj, ['foo', 'baz', 'qux']) // => false\n */\nexport function hasOwnProperties(obj, properties) {\n if(!isArray(properties)) properties = [properties]\n for (let i = 0; i < properties.length; i++) {\n if (!obj.hasOwnProperty(properties[i])) return false\n }\n return true\n}\n\n/**\n * Finds the closest number to the set goal in an array to a given number\n * \n * @param {number} goal Number to search for\n * @param {array} arr Array of numbers to search in\n * @returns number\n * @example\n * closestNumber(10, [1, 2, 3, 4, 5, 6, 7, 8, 9]) // => 9\n * closestNumber(10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]) // => 9\n * closestNumber(10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 9.5]) // => 9.5\n * closestNumber(10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) // => 10\n */\nexport function closestNumber(goal, arr) {\n return arr.reduce(function(prev, curr) {\n return Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev\n })\n}\n\n/**\n * Truncate a string to a given number of words\n * \n * @param {string} str String to truncate\n * @param {number} numWords Number of words to truncate to\n * @param {string} ellipsis Ellipsis to append to the end of the string\n * @returns string\n * @example\n * truncateString('foo bar baz', 2) // => 'foo bar\u2026'\n * truncateString('foo bar baz', 2, '...') // => 'foo bar...'\n * truncateString('foo bar. baz', 2, '...') // => 'foo bar. ...'\n */\nexport function truncateString(str, numWords, ellipsis = '\u2026') {\n const words = str.trim().split(' ')\n if (words.length <= numWords) return str\n if (numWords <= 0) return ''\n if (/[.?!]$/.test(words[numWords - 1]) && ellipsis.trim() !== '') ellipsis = ` ${ellipsis}`\n return words.slice(0, numWords).join(' ') + ellipsis\n}\n\n/**\n * Generates a random integer between two values, inclusive of both\n * \n * @param {number} min Minimum value\n * @param {number} max Maximum value\n * @param {boolean} safe Defaults to false, if true will use a cryptographically secure random number generator\n * @returns number\n * @example\n * randomIntInclusive(1, 10) // => 1\n * randomIntInclusive(1, 10) // => 10\n * randomIntInclusive(1, 10) // => 5\n */\nexport function randomIntInclusive(min, max, safe = false) {\n min = Number(min)\n max = Number(max)\n if (isNaN(min) || isNaN(max)) throw new TypeError('Both min and max must be numbers')\n if (min > max) [min, max] = [max, min]\n if (min === max) return min\n min = Math.round(min)\n max = Math.round(max)\n const rand = safe ? random() : Math.random()\n return Math.floor(rand * (max - min + 1)) + min\n}\n\n/**\n * Gets fixed number of digits after the decimal point\n * \n * @param {number} number Number to fix\n * @param {number} digits Number of digits to fix to\n * @returns number\n * @example\n * fixed(1.234, 2) // => 1.23\n * fixed(1.235, 2) // => 1.24\n * fixed(1.234) // => 1\n * fixed(1.234, 0) // => 1\n * fixed(1.234, 5) // => 1.234\n * @note Gotta ask myself why I wrote this function in the first place... \uD83E\uDD14 It's just not useful in a lot of cases lol...\n */\nexport function fixed(number, digits) {\n if (!digits) return parseInt(number)\n return parseFloat(number.toFixed(digits))\n}\n\n/**\n * Calculates the percentage of a number in relation to another number\n * \n * @param {number} num Number to calculate percentage of\n * @param {number} total Total number\n * @returns number\n * @example\n * percentage(1, 10) // => 10\n * percentage(5, 10) // => 50\n * percentage(10, 10) // => 100\n * percentage(0, 10) // => 0\n * percentage(10, 2) // => 500\n */\nexport function percentage(num, total) {\n if (!num || !total || Number.isNaN(num) || Number.isNaN(total)) return 0\n return num / total * 100\n}\n\nexport function pickProperties(obj, props) {\n const res = {}\n if (!props) return res\n if (!isArray(props)) props = [props]\n for (let i = 0; i < props.length; i++) {\n if (obj.hasOwnProperty(props[i])) res[props[i]] = obj[props[i]]\n }\n return res\n}\n\nexport function rejectProperties(obj, props, clone = true) {\n if (clone) obj = { ...obj }\n if (!props) return obj\n if (!isArray(props)) props = [props]\n for (let i = 0; i < props.length; i++) {\n if (obj.hasOwnProperty(props[i])) delete obj[props[i]]\n }\n return obj\n}\n\nexport function pickArrayElements(arr, indexes) {\n if (!isArray(arr)) return\n if (!isArray(indexes)) indexes = [indexes]\n const res = []\n for (let i = 0; i < indexes.length; i++) {\n if (arr.hasOwnProperty(indexes[i])) res.push(arr[indexes[i]])\n }\n return res\n}\n\nexport function rejectArrayElements(arr, indexes, clone = true) {\n if (clone) arr = [...arr]\n if (!isArray(arr)) return\n if (!isArray(indexes)) indexes = [indexes]\n for (let i = indexes.length - 1; i >= 0; i--) {\n if (arr.hasOwnProperty(indexes[i])) arr.splice(indexes[i], 1)\n }\n return arr\n}\n\n/**\n * Pick properties from an object or elements from an array\n * \n * @param {array} obj Object or array to pick properties or elements from\n * @param {array | string | number} props Properties to remove, can be an array of strings or a single string or number\n * @returns object | array | undefined\n * @example\n * \n * pick({ foo: 'bar', bar: 'baz', baz: 'qux' }) // => {}\n * pick({}, []) // => {}\n * pick(null, 'foo') // => undefined\n * pick({ foo: 'bar', bar: 'baz', baz: 'qux' }, undefined) // => {}\n * pick({ foo: 'bar', bar: 'baz', baz: 'qux' }, 'foo') // => { foo: 'bar'}\n * pick({ foo: 'bar', bar: 'baz', baz: 'qux' }, ['foo', 'baz']) // => { foo: 'bar', baz: 'qux' }\n * \n * pick(['foo', 'bar', 'baz'], []) // => []\n * pick([], []) // => []\n * pick(null, 0) // => undefined\n * pick(['foo', 'bar', 'baz'], undefined) // => []\n * pick(['foo', 'bar', 'baz'], 0) // => ['foo']\n * pick(['foo', 'bar', 'baz'], [0, 2]) // => ['foo', 'baz']\n * pick(['foo', 'bar', 'baz'], [0, 2, 3]) // => ['foo', 'baz']\n */\nexport function pick(obj, props) {\n return isObject(obj) ? pickProperties(obj, props) : pickArrayElements(obj, props)\n}\n\n/**\n * Remove properties from an object or elements from an array\n * \n * @param {array} obj Object or array to remove properties or elements from\n * @param {array | string | number} props Properties to remove, can be an array of strings or a single string or number\n * @param {boolean} clone Defaults to true, will clone the object or array before removing properties or elements.\n * @returns object | array | undefined\n * @example\n * \n * reject({ foo: 'bar', bar: 'baz', baz: 'qux' }) // => {}\n * reject({}, []) // => {}\n * reject(null, 'foo') // => undefined\n * reject({ foo: 'bar', bar: 'baz', baz: 'qux' }, undefined) // => {}\n * reject({ foo: 'bar', bar: 'baz', baz: 'qux' }, 'foo') // => { bar: 'baz', baz: 'qux' }\n * reject({ foo: 'bar', bar: 'baz', baz: 'qux' }, ['foo', 'baz']) // => { bar: 'baz' }\n * \n * reject(['foo', 'bar', 'baz'], []) // => []\n * reject([], []) // => []\n * reject(null, 0) // => undefined\n * reject(['foo', 'bar', 'baz'], undefined) // => []\n * reject(['foo', 'bar', 'baz'], 0) // => ['bar', 'baz']\n * reject(['foo', 'bar', 'baz'], [0, 2]) // => ['bar']\n * reject(['foo', 'bar', 'baz'], [0, 2, 3]) // => ['bar']\n */\nexport function reject(obj, props, clone = true) {\n return isObject(obj) ? rejectProperties(obj, props, clone) : rejectArrayElements(obj, props, clone)\n}\n\n/**\n * Basic timestamp first UID generator that's good enough for most use cases but not for security purposes.\n * There's an extremely small chance of collision, so create a map object to check for collisions if you're worried about that.\n * \n * - `Date.now().toString(16)` is used for the timestamp, which is a base16 representation of the current timestamp in milliseconds.\n * - `random().toString(16).substring(2)` is used for the random number, which is a base16 representation of a random number between 0 and 1, with the first two characters removed.\n * \n * @param {boolean} safe Defaults to false, if true will use a cryptographically secure random number generator for the random number improving security but reducing performance. If crypto is not available, will use Math.random() instead.\n * @returns string\n * @example\n * basicUID() // => '18d4613e4d2-750bf066ac6158'\n */\nexport function basicUID(safe = false) {\n const rand = safe ? random() : Math.random()\n return Date.now().toString(16)+'-'+rand.toString(16).substring(2)\n}\n\nfunction cryptoUUIDFallback() {\n return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>\n (c ^ Math.random() * 16 >> c / 4).toString(16)\n )\n}\n\n// Taken from https://stackoverflow.com/a/2117523/5437943\nfunction cryptoRandomUUIDFallback() {\n return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>\n (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)\n )\n}\n\n/**\n * Generates a UUID v4\n * - Uses crypto.randomUUID if available\n * - Uses crypto.getRandomValues if available\n * - Uses a fallback if neither is available, which is not safe because it uses Math.random() instead of a cryptographically secure random number generator\n * \n * I'm bad at crypto and bitwise operations, not my cup of tea, so I had to rely on StackOverflow for the fallback: https://stackoverflow.com/a/2117523/5437943\n * \n * @param {boolean} safe Defaults to true, if false will use a fallback that's not cryptographically secure but significantly faster\n * @returns string\n * @example\n * generateUUID() // UUID v4, example 09ed0fe4-8eb6-4c2a-a8d3-a862b7513294\n */\nexport function generateUUID(safe = true) {\n if (!crypto || !safe) return cryptoUUIDFallback()\n if (crypto.randomUUID) return crypto.randomUUID()\n if (crypto.getRandomValues) return cryptoRandomUUIDFallback();\n}\n\n/**\n * Generates a random number between 0 and 1, inclusive of 0 but not inclusive of 1.\n * \n * - Uses crypto.getRandomValues if available\n * - Uses Math.random() if crypto.getRandomValues is not available\n * \n * @returns number\n * @example\n * random() // => 0.123456789\n */\nexport function random() {\n if (!crypto) return Math.random()\n if (crypto.getRandomValues) return crypto.getRandomValues(new Uint32Array(1))[0] / 4294967295 // 2^32 - 1 = 4294967295\n}\n", "/**\n * @module regex\n */\n\n/**\n * Regular expression for matching a YouTube video links and extracting their ID, works with both embed and watch URLs\n */\nexport const RE_YOUTUBE = /(?:youtube\\.com\\/(?:[^\\/]+\\/.+\\/|(?:v|e(?:mbed)?)\\/|.*[?&]v=)|youtu\\.be\\/)([^\"&?\\/ ]{11})/i\n\n/**\n * Regular expression for matching a Vimeo video links and extracting their ID, works with both embed and watch URLs, channels and groups\n */\nexport const RE_VIMEO = /(?:www\\.|player\\.)?vimeo.com\\/(?:channels\\/(?:\\w+\\/)?|groups\\/(?:[^\\/]*)\\/videos\\/|album\\/(?:\\d+)\\/video\\/|video\\/|)(\\d+)(?:[a-zA-Z0-9_\\-]+)?/i\n\n/**\n * Regular expression for matching a video URLs\n */\nexport const RE_VIDEO = /\\/([^\\/]+\\.(?:mp4|ogg|ogv|ogm|webm|avi))\\s*$/i\n\n/**\n * Regular expression for matching a image URLs\n */\nexport const RE_IMAGE = /\\/([^\\/]+\\.(?:jpg|jpeg|png|gif|svg|webp))\\s*$/i\n\n/**\n * Regular expression for matching a URL parameters\n */\nexport const RE_URL_PARAMETER = /([^\\s=&]+)=?([^&\\s]+)?/\n\n/**\n * Regular expression for matching a HTML attribute and tag names, also for matching shortcode attributes and names\n */\nexport const RE_ATTRIBUTES = /\\s*(?:([a-z_]{1}[a-z0-9\\-_]*)=?(?:\"([^\"]+)\"|'([^']+)')*)\\s*/gi\n\n/**\n * Regular expression for matching a single attribute without value\n */\nexport const RE_ATTRIBUTE_WITHOUT_VALUE = /^\\s*([a-z_]{1}[a-z0-9\\-_]*)\\s*$/i\n\n/**\n * Regular expression for matching a single attribute with value\n */\nexport const RE_ATTRIBUTE_WITH_VALUE = /^\\s*([a-z_]{1}[a-z0-9\\-_]*)=(\"[^\"]+\"|'[^']+')\\s*$/i\n\n/**\n * Regular expression for matching the first or last quote of a string used for removing them\n */\nexport const RE_FIRST_OR_LAST_QUOTE = /^[\"']|[\"']$/g\n", "/** @module parsers */\n\nimport { isArray, isObject, isString, stringToType } from './helpers.mjs'\nimport { RE_ATTRIBUTES, RE_ATTRIBUTE_WITHOUT_VALUE, RE_ATTRIBUTE_WITH_VALUE, RE_URL_PARAMETER, RE_FIRST_OR_LAST_QUOTE } from './regex.mjs'\nimport { decodeHTML, encodeHTML } from './dom.mjs'\n\n/**\n * Parse a string of attributes and return an object\n * \n * @param {string} str\n * @returns object\n * @example\n * parseAttributes('button text=\"Click me\" data='{\"key\": \\\"value\"}' class=\"btn btn-primary\"')\n * // => { button: null, text: 'Click me', data: '{\"key\": \"value\"}', class: 'btn btn-primary' }\n */\nexport function parseAttributes(str) {\n\tconst re = RE_ATTRIBUTES\n\tconst reWithoutValue = RE_ATTRIBUTE_WITHOUT_VALUE\n\tconst reHasValue = RE_ATTRIBUTE_WITH_VALUE\n\tconst reReplaceFirstAndLastQuote = RE_FIRST_OR_LAST_QUOTE\n\t\n\tconst res = {}\n\tconst match = str.match(re)\n\n\tfor (let i = 0; i < match.length; i++) {\n\t\tconst m = match[i]\n\t\tif (m === '') continue\n\n\t\tif (reWithoutValue.test(m)) {\n\t\t\tconst [, key] = m.match(reWithoutValue)\n\t\t\tres[key] = null\n\t\t\treWithoutValue.lastIndex = 0\n\t\t} else if (reHasValue.test(m)) {\n\t\t\tconst [, key, value] = m.match(reHasValue)\n\t\t\tres[key] = stringToType(decodeHTML(value.replace(reReplaceFirstAndLastQuote, '')))\n\t\t\treReplaceFirstAndLastQuote.lastIndex = 0\n\t\t\treHasValue.lastIndex = 0\n\t\t}\n\t}\n\n\treturn res\n}\n\n/**\n * Serialize an object of key value pairs into a string of attributes\n * \n * @param {object} obj - The object to serialize\n * @returns {string} of attributes\n * @example\n * serializeAttributes({ button: null, text: 'Click me', data: '{\"key\": \"value\"}', class: 'btn btn-primary' }) // button text=\"Click me\" data=\"{\\\"key\\\": \\\"value\\\"}\" class=\"btn btn-primary\"\n */\nexport function serializeAttributes(obj) {\n\tconst res = []\n\n\tObject.keys(obj).forEach((key) => {\n\t\tlet value = obj[key]\n\t\tif (isObject(value) || isArray(value)) value = JSON.stringify(value)\n\t\tif (isString(value)) value = encodeHTML(value)\n\t\tconst valueString = value === null || value === undefined ? '' : `=\"${value}\"`\n\t\tres.push(`${key}${valueString}`)\n\t})\n\n\treturn res.join(' ')\n}\n\n/**\n * Encodes HTML entities in a string using the following rules:\n * \n * - & (ampersand) becomes &\n * - \" (double quote) becomes "\n * - ' (single quote) becomes '\n * - < (less than) becomes <\n * - > (greater than) becomes >\n * \n * It is different than dom.encodeHTML, which encodes all characters using the browser's DOMParser. This function only encodes the characters listed above and should be used when DOMParser is not available.\n * @see {@link module:dom.encodeHTML}\n * \n * @param {string} str - The string to encode\n * @returns {string} The encoded string\n * @example\n * htmlEncode('Link') // <a href="#">Link</a>\n */\nexport function encodeHtmlEntities(str) {\n\treturn str.replace(/[<>\"'\\&]/g, (m) => {\n\t\tswitch (m) {\n\t\t\tcase '<': return '<'\n\t\t\tcase '>': return '>'\n\t\t\tcase '\"': return '"'\n\t\t\tcase \"'\": return '''\n\t\t\tcase '&': return '&'\n\t\t}\n\t})\n}\n\n/**\n * Decodes HTML entities in a string using the following rules:\n * \n * - & becomes &\n * - " becomes \"\n * - ' becomes '\n * - < becomes <\n * - > becomes >\n * \n * It is different than dom.decodeHTML, which decodes all characters using the browser's DOMParser. This function only decodes the characters listed above and should be used when DOMParser is not available.\n * @see {@link module:dom.decodeHTML}\n * \n * @param {string} str - The string to decode\n * @returns {string} The decoded string\n * @example\n * htmlDecode('<a href="#">Link</a>') // Link\n */\nexport function decodeHtmlEntities(str) {\n\treturn str.replace(/<|>|"|'|&/g, (m) => {\n\t\tswitch (m) {\n\t\t\tcase '<': return '<'\n\t\t\tcase '>': return '>'\n\t\t\tcase '"': return '\"'\n\t\t\tcase ''': return \"'\"\n\t\t\tcase '&': return '&'\n\t\t}\n\t})\n}\n\n\n/**\n * Parses a string of url parameters into an object of key value pairs\n * \n * @param {string} paramString - The string to parse without ? or # and with & as separator\n * @param {boolean} [decode=true] - Whether to decode the values or not\n * @returns {object} of key value pairs\n * @example\n * parseUrlParams('foo=true&baz=555') // { foo: true, baz: 555 }\n * parseUrlParams('foo=bar&baz=qux', false) // { foo: 'true', baz: '555' }\n * parseUrlParams('foo&bar&baz=qux') // { foo: undefined, bar: undefined, baz: 'qux' }\n */\nexport function parseUrlParameters(paramString, decode = true) {\n const res = {}\n\n const paramParts = paramString.split('&')\n paramParts.forEach((part) => {\n const m = part.match(RE_URL_PARAMETER)\n\t\tif (!m) return\n const key = m[1]\n const value = m[2]\n res[key] = value !== undefined && decode ? stringToType(decodeURIComponent(value)) : stringToType(value)\n\t\tRE_URL_PARAMETER.lastIndex = 0\n })\n\n return res\n}\n\n/**\n * Serialize an object of key value pairs into a string of url parameters\n * \n * @param {object} obj - The object to serialize\n * @param {boolean} [encode=true] - Whether to encode the values or not\n * @returns {string} of url parameters\n * @example\n * serializeUrlParams({ foo: true, baz: 555 }) // foo=true&baz=555\n * serializeUrlParams({ bar: undefined, baz: 'qux' }, false) // bar=&baz=qux\n */\nexport function serializeUrlParameters(obj, encode = true) {\n\tconst res = []\n\n\tObject.keys(obj).forEach((key) => {\n\t\tconst value = obj[key]\n\t\tif (value === undefined) return res.push(key)\n\t\tconst encodedValue = encode ? encodeURIComponent(value) : value\n\t\tres.push(`${key}=${encodedValue}`)\n\t})\n\n\treturn res.join('&')\n}\n\n/**\n * Parses a resolution string into a number. Resolution string is in the format of 'width:height', e.g. '16:9' \n * \n * @param {string} res Resolution string. Format is 'width:height', e.g. '16:9', or 'widthxheight', e.g. '16x9', or 'width-height', e.g. '16-9', or 'width/height', e.g. '16/9'\n * @returns number\n * @example\n * parseResolutionString('16:9') // => 1.7777777778\n * parseResolutionString('4:3') // => 1.3333333333\n * parseResolutionString('4x3') // => 1.3333333333\n * parseResolutionString('4-3') // => 1.3333333333\n */\nexport function parseResolutionString(res) {\n const DEFAULT_RESOLUTION = 1.7777777778 // 16:9\n if (!res || !res.length || /16[\\:x\\-\\/]{1}9/i.test(res)) return DEFAULT_RESOLUTION\n const pts = res.split(/\\s?[\\:x\\-\\/]{1}\\s?/i)\n if (pts.length < 2) return DEFAULT_RESOLUTION\n\n const w = parseInt(pts[0])\n const h = parseInt(pts[1])\n\n if (w === 0 || h === 0) return DEFAULT_RESOLUTION\n if (isNaN(w) || isNaN(h)) return DEFAULT_RESOLUTION\n\n return w/h;\n}\n", "/** @module dom */\n\nimport { transformDashToCamelCase, isArray, isString, isObject, isFunction, shallowMerge, percentage } from './helpers.mjs'\nimport { encodeHtmlEntities, decodeHtmlEntities } from './parsers.mjs'\n\n/**\n * Checks if an element is empty\n * \n * @param {HTMLElement} element \n * @returns boolean\n * @example\n * document.body.innerHTML = `\n *
\n *
foo
\n *

`\n * \n * isEmptyElement(document.getElementById('empty-element')) // => true\n * isEmptyElement(document.getElementById('non-empty-element1')) // => false\n * isEmptyElement(document.getElementById('non-empty-element2')) // => false\n */\nexport function isEmptyElement(element) {\n return element.innerHTML.trim() === ''\n}\n\n/**\n * Removes all elements matching a selector from the DOM\n * \n * @param {string|HTMLElement|Element} selector The selector to select elements to remove\n * @param {HTMLElement|Element} [from=document] The element to remove elements from\n * @example\n * document.body.innerHTML = `\n *
\n *
\n *
`\n * `\n * remove('#foo, #bar') // => removes #foo and #bar\n */\nexport function remove(selector, from = document) {\n const elements = query(selector, from)\n for (const element of elements) {\n element.remove()\n }\n}\n\n/**\n * Queries the DOM for a single element and returns it. Substitutes for `document.querySelector(selector)` and JQuery's `$(selector).first()`\n * \n * @param {string|HTMLElement|Element|Array|NodeList} selector The selector to select an element\n * @param {HTMLElement|Element} [from=document] The element to query from\n * @returns {HTMLElement|Element}\n * @example\n * document.body.innerHTML = `\n *
\n *
\n *
`\n * \n * querySingle('#foo') // =>
\n * querySingle(document.getElementById('foo')) // =>
\n * querySingle(document.querySelector('#foo')) // =>
\n */\nexport function querySingle(selector, from = document) {\n if (selector instanceof Element) return selector\n return from.querySelector(selector)\n}\n\n/**\n * Queries the DOM for elements and returns them. Substitutes for `document.querySelectorAll(selector)` and JQuery's `$(selector)`\n * \n * @param {string|HTMLElement|Element|Array|NodeList} selector The selector to select elements\n * @param {HTMLElement|Element} [from=document] The element to query from\n * @returns {Array|NodeList}\n * @example\n * document.body.innerHTML = `\n *
\n *
\n *
`\n * \n * query('#foo') // => [
]\n * query(document.getElementById('foo')) // => [
]\n * query('div') // => [
,
,
]\n */\nexport function query(selector, from = document) {\n if (selector instanceof Array || selector instanceof NodeList) return selector\n if (selector instanceof Element) return [selector]\n if (from instanceof Element || from instanceof Document) return from.querySelectorAll(selector)\n if (isString(from)) from = query(from)\n if (!from instanceof Array && !from instanceof NodeList) return []\n const res = []\n for (const element of from) {\n res.push(...element.querySelectorAll(selector))\n }\n return res\n}\n\n/**\n * Sets element styles from passed object of styles. Can also transform dash-case to camelCase for CSS properties\n * \n * @param {HTMLElement} element The element to set styles on\n * @param {object} styles The object of styles to set\n * @param {boolean} transform Whether to transform dash-case to camelCase for CSS properties\n * @example\n * css(document.getElementById('foo'), { 'background-color': 'red', 'font-size': '16px' }, true) // => sets background-color and font-size\n * css(document.getElementById('foo'), { backgroundColor: 'red', fontSize: '16px' }) // => sets background-color and font-size\n */\nexport function css(element, styles, transform = false) {\n if (!element || !styles) return\n for (let property in styles) {\n if (transform) property = transformDashToCamelCase(property)\n element.style[property] = styles[property]\n }\n}\n\n/**\n * Decodes HTML entities in a string using the browser's DOMParser. If the DOMParser is not available, it uses a regular expression to decode the basic entities.\n * \n * @see {@link module:parsers.decodeHtmlEntities}\n * \n * @param {string} html The HTML string to decode\n * @returns {string} The decoded HTML string\n * @example\n * decodeHTML('<div>foo</div>') // => '
foo
'\n * decodeHTML('<div>foo</div><div>bar</div>') // => '
foo
bar
'\n */\nexport function decodeHTML(html) {\n if (typeof document === 'undefined') return decodeHtmlEntities(html)\n const txt = document.createElement('textarea')\n txt.innerHTML = html\n const res = txt.value\n txt.remove()\n return res\n}\n\n/**\n * Encodes HTML entities in a string using the browser's DOMParser. If the DOMParser is not available, it uses a regular expression to encode the basic entities.\n * \n * @see {@link module:parsers.encodeHtmlEntities}\n * \n * @param {string} html The HTML string to encode\n * @returns {string} The encoded HTML string\n * @example\n * encodeHTML('
foo
') // => '<div>foo</div>'\n * encodeHTML('
foo
bar
') // => '<div>foo</div><div>bar</div>'\n */\nexport function encodeHTML(html) {\n if (typeof document === 'undefined') return encodeHtmlEntities(html)\n const txt = document.createElement('textarea')\n txt.textContent = html\n const res = txt.innerHTML\n txt.remove()\n return res\n}\n\n/**\n * Inserts an element before another element\n * \n * @param {HTMLElement} targetElement The element to insert before\n * @param {HTMLElement} newElement The element to insert\n * @example\n * const target = document.getElementById('target')\n * const newElement = document.createElement('div')\n * newElement.id = 'newElement'\n * insertBeforeElement(target, newElement)\n * //
\n * //
\n */\nexport function insertBeforeElement(targetElement, newElement) {\n if (!targetElement || !newElement) return\n targetElement.parentNode.insertBefore(newElement, targetElement);\n}\n\n/**\n * Toggles an attribute value on an element\n * \n * @param {HTMLElement} element The element to toggle the attribute on\n * @param {string} attribute The attribute to toggle\n * @param {string} on Default: 'true'\n * @param {string} off Default: 'false'\n * @example\n * toggleAttributeValue(element, 'aria-expanded', 'true', 'false')\n * toggleAttributeValue(element, 'aria-expanded')\n */\nexport function toggleAttributeValue(element, attribute, on = 'true', off = 'false') {\n if (!element.hasAttribute(attribute)) return\n\n if (element.getAttribute(attribute) === on) {\n element.setAttribute(attribute, off)\n } else {\n element.setAttribute(attribute, on)\n }\n}\n\n/**\n * Converts a duration string to milliseconds integer\n * \n * @param {string} duration The duration string to convert, e.g. '1s', '100ms', '0.5s'\n * @returns {number} The duration in milliseconds\n * @example\n * convertToMilliseconds('1s') // 1000\n * convertToMilliseconds('100ms') // 100\n * convertToMilliseconds('0.5s') // 500\n * convertToMilliseconds('0.5') // 0\n * convertToMilliseconds('foo') // 0\n */\nexport function cssTimeToMilliseconds(duration) {\n const regExp = new RegExp('([0-9.]+)([a-z]+)', 'i')\n const matches = regExp.exec(duration)\n if (!matches) return 0\n \n const unit = matches[2]\n switch (unit) {\n case 'ms':\n return parseFloat(matches[1])\n case 's':\n return parseFloat(matches[1]) * 1000\n default:\n return 0\n }\n}\n\n/**\n * Returns a map of transition properties and durations\n * \n * @param {HTMLElement} element The element to get the transition properties and durations from\n * @returns {object} A map of transition properties and durations\n * @example\n * getTransitionDurations(element) // { height: 1000 } if transition in CSS is set to 'height 1s'\n * getTransitionDurations(element) // { height: 500, opacity: 1000 } if transition in CSS is set to 'height 0.5s, opacity 1s'\n */\nexport function getTransitionDurations(element) {\n if (!element) {}\n const styles = getComputedStyle(element)\n const transitionProperties = styles.getPropertyValue('transition-property').split(',')\n const transitionDurations = styles.getPropertyValue('transition-duration').split(',')\n \n const map = {}\n \n for (let i = 0; i < transitionProperties.length; i++) {\n const property = transitionProperties[i].trim()\n map[property] = transitionDurations.hasOwnProperty(i) ? cssTimeToMilliseconds(transitionDurations[i].trim()) : null\n }\n \n return map\n}\n\n/**\n * Check a list of elements if any of them matches a selector\n * \n * @param {Array|NodeList|HTMLElement} elements The elements to check\n * @param {string} selector The selector to check\n * @returns {boolean} True if any of the elements matches the selector, false otherwise\n * @example\n * document.body.innerHTML = `\n *
\n *
\n *
`\n * \n * matchesAny(document.querySelectorAll('div'), '#foo') // => true\n * matchesAny(document.querySelectorAll('div'), '#qux') // => false\n */\nexport function matchesAny(elements, selector) {\n if (!elements || !selector || !elements.length) return false\n if (elements instanceof Element) elements = [elements]\n if (isString(elements)) elements = query(elements)\n for (const element of elements) {\n if (element.matches(selector)) return true\n }\n return false\n}\n\n/**\n * Check a list of elements if all of them matches a selector\n * \n * @param {Array|NodeList|HTMLElement} elements The elements to check\n * @param {string} selector The selector to check\n * @returns {boolean} True if all of the elements matches the selector, false otherwise\n * @example\n * document.body.innerHTML = `\n *
\n *
\n *
`\n * \n * matchesAll(document.querySelectorAll('div'), 'div') // => true\n * matchesAll(document.querySelectorAll('div'), '#foo') // => false\n */\nexport function matchesAll(elements, selector) {\n if (!elements || !selector || !elements.length) return false\n if (elements instanceof Element) elements = [elements]\n if (isString(elements)) elements = query(elements)\n for (const element of elements) {\n if (!element.matches(selector)) return false\n }\n return true\n}\n\n\n/**\n * Detaches an element from the DOM and returns it\n * \n * @param {HTMLElement} element The element to detach\n * @example\n * detachElement(element)\n * // => element\n * console.log(element.parentNode) // => null\n */\nexport function detachElement(element) {\n if (element && element.parentNode) {\n element.parentNode.removeChild(element);\n }\n return element\n}\n\n/**\n * Gets table data from a table element, a simple regular table element, or a table like structure.\n * Useful for scraping data.\n * \n * @param {string} selector The selector to select the table element\n * @param {Array|string|null} headers The headers to use for the data. If 'auto' is passed, the row containing th or the first row will be used as headers\n * @param {string} [rowSelector='tr'] The selector to select the rows\n * @param {string} [cellSelector='td'] The selector to select the cells\n * @returns {Array} An array of objects with the properties as keys and the cell values as values\n * @example\n * document.body.innerHTML = `\n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n *
FooBar
Foo 1Bar 1
Foo 2Bar 2
`\n * \n * getTableData('#table', ['foo', 'bar'])\n * // => [\n * // { foo: 'Foo 1', bar: 'Bar 1' },\n * // { foo: 'Foo 2', bar: 'Bar 2' }\n * // ]\n */\nexport function getTableData(selector, headers, rowSelector = 'tr', cellSelector = 'td', headerCellSelector = 'th') {\n const table = typeof selector === 'string' ? document.querySelector(selector) : selector\n const res = []\n const rows = table.querySelectorAll(rowSelector)\n let start = 0\n\n function iterateHeaders(arr) {\n if (!arr || !arr.length) return\n const res = []\n for (let i = 0; i < arr.length; i++) {\n res.push(arr[i].textContent.trim())\n }\n return res\n }\n\n if (headers && isString(headers) && headers === 'auto') {\n let headerCells = table.querySelectorAll(headerCellSelector)\n \n if (headerCells && headerCells.length) {\n headers = iterateHeaders(headerCells)\n } else {\n headers = iterateHeaders(rows[0].querySelectorAll(cellSelector))\n start = 1\n }\n }\n\n for (let i = start; i < rows.length; i++) {\n const row = rows[i]\n const cells = row.querySelectorAll(cellSelector)\n if (!cells || !cells.length) continue\n\n let rowData = []\n if (headers && isArray(headers) && headers.length) {\n rowData = {}\n for (let j = 0; j < headers.length; j++) {\n rowData[headers[j]] = cells[j] ? cells[j].textContent.trim() : null\n }\n } else {\n for (let j = 0; j < cells.length; j++) {\n rowData.push(cells[j].textContent.trim())\n }\n }\n res.push(rowData)\n }\n return res\n}\n\n/**\n * Parses HTML string to a DOM Node\n * \n * @param {string} html The HTML string to parse\n * @param {boolean} [allChildren=false] If true, all children of the body will be returned, otherwise only the first child\n * @returns {Node} The parsed DOM Node\n * @example\n * parseDOM('
foo
') // =>
foo
\n * parseDOM('
foo
bar
', true) // => NodeList(2)\u00A0[div, div]\n * parseDOM(document.getElementById('foo')) // =>
\n * parseDOM(document.querySelectorAll('div')) // => NodeList(2)\u00A0[div, div]\n */\nexport function parseDOM(html, allChildren) {\n if (html instanceof Element || html instanceof NodeList) return html\n const parser = new DOMParser()\n const doc = parser.parseFromString(html, 'text/html')\n return !allChildren ? doc.body.firstChild : doc.body.childNodes\n}\n\n/**\n * Loads an image form a provided source url and calls a callback when it's loaded\n * \n * @param {string} src The source url of the image\n * @param {Function} [callback] The callback to call when the image is loaded\n * @example\n * loadImage('https://example.com/image.png', () => {\n * console.log('Image loaded')\n * })\n */\nexport function loadImage(src, callback) {\n const img = new Image()\n if (callback)\n img.addEventListener('load', callback, false);\n img.src = src\n}\n\n/**\n * Delegate DOM events using MutationObserver with a fallback to document.addEventListener\n * \n * @param {string} selector The selector to select the elements to delegate the event to\n * @param {string} eventType The event type to delegate, like `click`\n * @param {Function} handler The handler to call when the event is triggered.\n * @returns {MutationObserver | null} The MutationObserver instance\n * @example\n * delegateEvent('.foo', 'click', (e, target) => {\n * console.log('Clicked on', target)\n * })\n */\nexport function delegateEvent(selector, eventType, handler) {\n if (typeof MutationObserver === 'undefined') {\n document.addEventListener(eventType, (e) => {\n const target = e.target.closest(selector)\n if (target) handler(e, target)\n })\n\n return null\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (!(node instanceof HTMLElement)) continue\n if (!node.matches(selector)) continue\n node.addEventListener(eventType, (e) => {\n handler(e, e.currentTarget)\n })\n }\n }\n })\n\n for (const node of document.querySelectorAll(selector)) {\n node.addEventListener(eventType, (e) => {\n handler(e, e.currentTarget)\n })\n }\n\n observer.observe(document.body, { childList: true, subtree: true })\n return observer\n}\n\n/**\n * Run a handler on selected elements and on elements added to the DOM with the same selector, \n * or can be delegateEvent alias.\n * \n * @param {string} selector The selector to select the elements to run the handler on\n * @param {string | Function} eventTypeOrHandler The event type to delegate, like `click`, or the handler to call on every element\n * @param {Function} [handler] The handler to call when the event is triggered.\n * @returns {MutationObserver | null} The MutationObserver instance\n * @see delegateEvent\n * @example\n * on('.foo', (el) => {\n * console.log('Element', el, 'added to the DOM')\n * })\n * \n * on('.foo', 'click', (e, target) => {\n * console.log('Clicked on', target)\n * })\n */\nexport function on(selector, eventTypeOrHandler, handler) {\n if (isString(eventTypeOrHandler)) {\n return delegateEvent(selector, eventTypeOrHandler, handler)\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (!(node instanceof HTMLElement)) continue\n if (!node.matches(selector)) continue\n eventTypeOrHandler(node)\n }\n }\n })\n\n for (const node of document.querySelectorAll(selector)) {\n eventTypeOrHandler(node)\n }\n\n observer.observe(document.body, { childList: true, subtree: true })\n\n return observer\n}\n\n/**\n * Adds one listener to multiple events\n * \n * @param {string|HTMLElement|NodeList} elements The elements or a selector for elements to add the event listeners to\n * @param {string|Array} events The event types to add the event listeners for, like `click mouseenter`\n * @param {Function} handler The handler to call when the event is triggered.\n * @param {object} [options] The options to pass to the event listeners\n * @example\n * addListenerForEvents('.foo', 'click mouseenter', (e) => { console.log(e.type) })\n */\nexport function addListenerForEvents(elements, events, handler, options) {\n if (elements instanceof Element) elements = [elements]\n if (typeof elements === 'string') elements = query(elements)\n\n const eventTypes = isArray(events) ? events : events.split(' ')\n for (const element of elements) {\n for (const eventType of eventTypes) {\n element.addEventListener(eventType, handler, options)\n }\n }\n}\n\n/**\n * Removes one listener from multiple registered events\n * \n * @param {string|HTMLElement|NodeList} elements The elements or a selector for elements to remove the event listeners from\n * @param {string|Array} events The event types to remove the event listeners for, like `click mouseenter`\n * @param {Function} handler The handler to remove\n * @param {object} [options] The options to pass to the event listeners\n * @example\n * removeListenerForEvents('.foo', 'click mouseenter', (e) => { console.log(e.type) })\n */\nexport function removeListenerForEvents(elements, events, handler, options) {\n if (elements instanceof Element) elements = [elements]\n if (typeof elements === 'string') elements = query(elements)\n\n const eventTypes = isArray(events) ? events : events.split(' ')\n for (const element of elements) {\n for (const eventType of eventTypes) {\n element.removeEventListener(eventType, handler, options)\n }\n }\n}\n\n/**\n * Resizes an element to cover its parent element while maintaining the aspect ratio\n * \n * @param {string|HTMLElement|NodeList} elements The elements or a selector for elements to resize\n * @param {number} [ratio=1] The ratio to maintain\n * @param {number} [offset=0] An offset to add to the parent element's width and height\n * @example\n * proportionalParentCoverResize('.foo', 16/9, 10)\n */\nexport function proportionalParentCoverResize(elements, ratio = 1, offset = 0) {\n if (elements instanceof Element) elements = [elements]\n if (typeof elements === 'string') elements = query(elements)\n\n for (const element of elements) {\n const h = element.parentNode.offsetHeight + offset\n const w = element.parentNode.offsetWidth + offset\n\n if (ratio > w/h) {\n element.style.width = h*ratio + 'px'\n element.style.height = h + 'px'\n } else {\n element.style.width = w + 'px'\n element.style.height = w/ratio + 'px'\n }\n }\n}\n\n/**\n * If provided element is visible. Checks if the element is not visibility hidden or display none, has no opacity, and has a width and height.\n * \n * @param {HTMLElement} element The element to check\n * @returns {boolean} True if the element is visible, false otherwise\n * \n * @example\n * isVisible(document.getElementById('foo'))\n */\nexport function isVisible(element) {\n if (!element) return false;\n const computedStyle = getComputedStyle(element);\n if (computedStyle.getPropertyValue('display') === 'none') return false;\n if (element.getAttribute('hidden') !== null || computedStyle.getPropertyValue('visibility') === 'hidden' || computedStyle.getPropertyValue('opacity') == \"0\") return false;\n return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length)\n}\n\n/**\n * Swipe event handler\n * \n * @param {HTMLElement} element The element to listen for swipe gestures on\n * @param {object | Function} callback The callback to call when a swipe gesture is detected or the options object with the callback, threshold, and timeThreshold\n * @param {number} [threshold=150] The threshold in pixels to trigger the callback.\n * @param {number} [timeThreshold=0] The threshold in milliseconds to trigger the callback. Defaults to 0, which means the callback will be called regardless of the time it took to swipe.\n * @returns {object} The destroy method to remove the event listeners\n * @example\n * swipe(document.getElementById('foo'), (e) => {\n * console.log(e.direction)\n * console.log(e.deltaX)\n * console.log(e.deltaY)\n * console.log(e.startX)\n * console.log(e.startY)\n * console.log(e.endX)\n * console.log(e.endY)\n * console.log(e.threshold)\n * console.log(e.type)\n * console.log(e.target)\n * console.log(e.horizontal)\n * console.log(e.vertical)\n * console.log(e.horizontalDirection)\n * console.log(e.verticalDirection)\n * console.log(e.timeElapsed)\n * console.log(e.timeThreshold)\n * })\n * \n * element.addEventListener('swipe', (e) => { ... })\n * element.addEventListener('swipestart', (e) => { ... })\n * element.addEventListener('swipeend', (e) => { ... })\n */\nexport function swipe(element, callback, threshold = 150, timeThreshold = 0) {\n let startX = 0\n let startY = 0\n let endX = 0\n let endY = 0\n let startTime = 0\n let endTime = 0\n\n if (isObject(callback)) {\n const options = callback\n callback = options.callback\n threshold = options.threshold || threshold\n timeThreshold = options.timeThreshold || timeThreshold\n }\n\n if (!element) return\n if (element.getAttribute('swipe-enabled') === 'true') return\n element.setAttribute('swipe-enabled', 'true')\n\n const handleStart = function(e) {\n const carrier = e.touches ? e.touches[0] : e\n startX = carrier.clientX\n startY = carrier.clientY\n startTime = Date.now();\n element.dispatchEvent(new CustomEvent('swipestart', { detail: { target: element, startX, startY, startTime } }))\n }\n\n const handleEnd = function(e) {\n const carrier = e.changedTouches ? e.changedTouches[0] : e\n endX = carrier.clientX\n endY = carrier.clientY\n endTime = Date.now();\n handleSwipeGesture()\n element.dispatchEvent(new CustomEvent('swipeend', { detail: { target: element, startX, startY, startTime, endX, endY, endTime } }))\n }\n\n const handleSwipeGesture = function() {\n const deltaX = Math.abs(endX - startX)\n const deltaY = Math.abs(endY - startY)\n const horizontal = deltaX > threshold\n const vertical = deltaY > threshold\n const left = endX < startX\n const up = endY < startY\n const direction = []\n const timeElapsed = endTime - startTime;\n \n if (horizontal) direction.push(left ? 'left' : 'right')\n if (vertical) direction.push(up ? 'up' : 'down')\n\n let condition = direction.length && callback\n if (timeThreshold) condition = condition && timeElapsed <= timeThreshold\n \n if (condition) {\n const res = {\n target: element,\n deltaX: deltaX,\n deltaY: deltaY,\n startX: startX,\n startY: startY,\n endX: endX,\n endY: endY,\n threshold: threshold,\n horizontal: horizontal,\n vertical: vertical,\n horizontalDirection: left ? 'left' : 'right',\n verticalDirection: up ? 'up' : 'down',\n direction: direction.length === 1 ? direction[0] : direction,\n timeElapsed: timeElapsed,\n timeThreshold: timeThreshold\n }\n\n callback(res)\n element.dispatchEvent(new CustomEvent('swipe', { detail: res })) \n }\n }\n\n element.addEventListener('touchstart', handleStart)\n element.addEventListener('touchend', handleEnd)\n element.addEventListener('mousedown', handleStart)\n element.addEventListener('mouseup', handleEnd)\n\n return {\n destroy: function() {\n element.removeEventListener('touchstart', handleStart)\n element.removeEventListener('touchend', handleEnd)\n element.removeEventListener('mousedown', handleStart)\n element.removeEventListener('mouseup', handleEnd)\n }\n }\n}\n\n/**\n * Alias for swipe\n * \n * @see swipe\n * @deprecated Use swipe instead\n */\nexport const onSwipe = swipe\n\n/**\n * Drag event handler\n * \n * @param {HTMLElement} element The element to listen for drag gestures on\n * @param {object | Function} opts The options object or the callback to call when a drag gesture is detected\n * @param {boolean} [opts.inertia=false] Whether to enable inertia\n * @param {boolean} [opts.bounce=false] Whether to enable bounce when inertia is enabled\n * @param {number} [opts.friction=0.9] The friction to apply when inertia is enabled\n * @param {number} [opts.bounceFactor=0.2] The bounce factor to apply when bounce is enabled\n * @param {boolean} [opts.preventDefaultTouch=true] Whether to prevent the default touch behavior\n * @param {Function} [opts.callback] The callback to call when a drag gesture is detected\n * @returns {object} The destroy method to remove the event listeners\n * @example\n * drag(document.getElementById('foo'), (e) => {\n * console.log(e.x)\n * console.log(e.y)\n * console.log(e.relativeX)\n * console.log(e.relativeY)\n * console.log(e.xPercentage)\n * console.log(e.yPercentage)\n * console.log(e.velocityX)\n * console.log(e.velocityY)\n * console.log(e.prevX)\n * console.log(e.prevY)\n * })\n * \n * element.addEventListener('drag', (e) => { ... })\n * element.addEventListener('dragstart', (e) => { ... })\n * element.addEventListener('dragend', (e) => { ... })\n * element.addEventListener('draginertia', (e) => { ... })\n * element.addEventListener('draginertiaend', (e) => { ... })\n */\nexport function drag(element, opts) {\n if (!element || !(element instanceof Element)) return\n if (element.getAttribute('drag-enabled') === 'true') return\n\n let x = 0\n let y = 0\n let prevX = 0\n let prevY = 0\n let velocityX = 0\n let velocityY = 0\n let dragging = false\n let rect = null\n let inertiaId = null\n\n const options = {\n inertia: false,\n bounce: false,\n friction: 0.9,\n bounceFactor: 0.2,\n callback: null,\n preventDefaultTouch: true\n }\n\n if (isFunction(opts)) {\n options.callback = opts\n } else if (isObject(opts)) {\n shallowMerge(options, opts)\n }\n\n options.friction = Math.abs(options.friction)\n options.bounceFactor = Math.abs(options.bounceFactor)\n\n element.setAttribute('drag-enabled', 'true')\n element.setAttribute('dragging', 'false')\n\n const calcPageRelativeRect = function() {\n const origRect = element.getBoundingClientRect()\n const rect = {\n top: origRect.top + window.scrollY,\n left: origRect.left + window.scrollX,\n width: origRect.width,\n height: origRect.height\n }\n\n return rect\n }\n rect = calcPageRelativeRect()\n\n const handleStart = function(e) {\n setXY(e)\n dragging = true\n rect = calcPageRelativeRect()\n element.setAttribute('dragging', 'true')\n if (inertiaId) {\n cancelAnimationFrame(inertiaId)\n inertiaId = null\n }\n const event = new CustomEvent('dragstart', { detail: getDetail() })\n element.dispatchEvent(event)\n }\n\n const handleMove = function(e) {\n if (!dragging) return\n setXY(e)\n velocityX = x - prevX\n velocityY = y - prevY\n const detail = getDetail()\n if (options.callback) options.callback(detail)\n const event = new CustomEvent('drag', { detail: detail })\n element.dispatchEvent(event)\n }\n\n const handleEnd = function() {\n dragging = false\n element.setAttribute('dragging', 'false')\n if (options.inertia) inertiaId = requestAnimationFrame(inertia)\n const event = new CustomEvent('dragend', { detail: getDetail() })\n element.dispatchEvent(event)\n }\n\n const setXY = function(e) {\n const carrier = e.touches ? e.touches[0] : e\n if (e.touches && options.preventDefaultTouch) e.preventDefault()\n prevX = x\n prevY = y\n x = carrier.pageX\n y = carrier.pageY\n }\n\n const getDetail = function() {\n const relativeX = x - rect.left\n const relativeY = y - rect.top\n const xPercentage = percentage(relativeX, rect.width)\n const yPercentage = percentage(relativeY, rect.height)\n\n const detail = {\n target: element,\n x: x,\n y: y,\n relativeX: relativeX,\n relativeY: relativeY,\n xPercentage: xPercentage,\n yPercentage: yPercentage,\n velocityX: velocityX,\n velocityY: velocityY,\n prevX: prevX,\n prevY: prevY\n }\n\n if (xPercentage < 0) detail.xPercentage = 0\n if (xPercentage > 100) detail.xPercentage = 100\n if (yPercentage < 0) detail.yPercentage = 0\n if (yPercentage > 100) detail.yPercentage = 100\n\n return detail\n }\n\n const inertia = function() {\n x += velocityX\n y += velocityY\n velocityX *= options.friction\n velocityY *= options.friction\n\n if (options.bounce) {\n if (x < rect.left) {\n x = rect.left\n velocityX *= -options.bounceFactor\n }\n if (x > rect.width + rect.left) {\n x = rect.width + rect.left\n velocityX *= -options.bounceFactor\n }\n if (y < rect.top) {\n y = rect.top\n velocityY *= -options.bounceFactor\n }\n if (y > rect.height + rect.top) {\n y = rect.height + rect.top\n velocityY *= -options.bounceFactor\n }\n }\n\n if (Math.abs(velocityX) < 0.1) velocityX = 0\n if (Math.abs(velocityY) < 0.1) velocityY = 0\n\n const detail = getDetail()\n\n if (velocityX !== 0 || velocityY !== 0) {\n if (options.callback) options.callback(detail)\n const event = new CustomEvent('draginertia', { detail: detail })\n element.dispatchEvent(event)\n inertiaId = requestAnimationFrame(inertia)\n } else {\n inertiaId = null\n if (options.callback) options.callback(detail)\n const event = new CustomEvent('draginertiaend', { detail: detail })\n element.dispatchEvent(event)\n }\n }\n\n element.addEventListener('mousedown', handleStart)\n element.addEventListener('mousemove', handleMove)\n element.addEventListener('mouseup', handleEnd)\n element.addEventListener('touchstart', handleStart)\n element.addEventListener('touchmove', handleMove)\n element.addEventListener('touchend', handleEnd)\n\n return {\n destroy: function() {\n element.removeEventListener('mousedown', handleStart)\n element.removeEventListener('mousemove', handleMove)\n element.removeEventListener('mouseup', handleEnd)\n element.removeEventListener('touchstart', handleStart)\n element.removeEventListener('touchmove', handleMove)\n element.removeEventListener('touchend', handleEnd)\n\n if (inertiaId) {\n cancelAnimationFrame(inertiaId)\n inertiaId = null\n }\n }\n }\n}\n\n/**\n * Alias for drag\n * \n * @see drag\n * @deprecated Use drag instead\n */\nexport const onDrag = drag\n", "/** @module browser */\n\nimport { isEmpty, isFunction } from './helpers.mjs'\nimport { css } from './dom.mjs'\nimport { parseUrlParameters } from './parsers.mjs'\n\nexport function isUserAgentIOS(str) {\n return /iPad|iPhone|iPod/i.test(str)\n}\n\nexport function isUserAgentMobile(str) {\n return /\\b(BlackBerry|webOS|iPhone|IEMobile)\\b/i.test(str) ||\n /\\b(Android|Windows Phone|iPad|iPod)\\b/i.test(str)\n}\n\nexport function isUserAgentSafari(str) {\n return /^((?!chrome|android|crios|fxios).)*safari/i.test(str)\n}\n\n/**\n * Check if the device is an iOS device\n * \n * @returns boolean True if the device is an iOS device, false otherwise\n */\nexport function isIOS() {\n return isUserAgentIOS(navigator.userAgent) && 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 1\n}\n\n/**\n * Check if the device is a mobile device\n * \n * @returns boolean True if the device is a mobile device, false otherwise\n */\nexport function isMobile() {\n if ('maxTouchPoints' in navigator) return navigator.maxTouchPoints > 0\n\n if ('matchMedia' in window) return !!matchMedia('(pointer:coarse)').matches\n\n if ('orientation' in window) return true\n\n return isUserAgentMobile(navigator.userAgent)\n}\n\n/**\n * Check if the browser is Safari\n *\n * @returns boolean True if the browser is Safari, false otherwise\n */\nexport function isSafari() {\n if (navigator.hasOwnProperty('vendor')) /apple/i.test(navigator.vendor)\n return isUserAgentSafari(navigator.userAgent)\n}\n\n/**\n * Check if the browser is Safari on iOS\n * \n * @returns boolean True if the browser is Safari on iOS, false otherwise\n */\nexport function isIOSSafari() {\n return isIOS() && isSafari()\n}\n\n/**\n * A wrapper for the matchMedia function, cause with `matchMedia` you can only either add a listener or check the media query\n * this function does both.\n * \n * @param {string} query The media query to check\n * @param {function} [callback] The callback function to call when the media query changes\n * @returns {boolean} The result of the media query\n * \n * @example\n * mediaMatcher('(min-width: 768px)', (matches) => {\n * if (matches) {\n * // Do something\n * } else {\n * // Do something else\n * }\n * })\n * \n * // Or\n * \n * const isDesktop = mediaMatcher('(min-width: 768px)')\n */\nexport function mediaMatcher(query, callback) {\n if (isFunction(callback)) {\n matchMedia(query).addEventListener('change', (e) => {\n callback(e.matches)\n })\n\n const mql = matchMedia(query)\n callback(mql.matches)\n\n return mql.matches\n }\n\n return matchMedia(query).matches\n}\n\n/**\n * Get the scrollbar width\n * \n * When preventing scroll with html overflow hidden the scroll bar will disappear and the whole page will shift (if the scroll bar is visible that is).\n * To substitute for the scrollbar width we can add a padding to the body element.\n * \n * @returns {number} The scrollbar width\n * \n * @example\n * const scrollbarWidth = getScrollbarWidth() // 15 (on MacOS X Safari)\n */\nexport function getScrollbarWidth() {\n const scrollDiv = document.createElement('div')\n \n css(scrollDiv, {\n width: '100px',\n height: '100px',\n position: 'absolute',\n left: '-9999px',\n zIndex: '0',\n overflowX: 'hidden',\n overflowY: 'scroll'\n })\n\n document.body.appendChild(scrollDiv)\n const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth\n document.body.removeChild(scrollDiv)\n return scrollbarWidth\n}\n\n/**\n * Check if the vertical scrollbar is visible\n * \n * @param {number} [scrollbarWidth] The width of the scrollbar, defaults to getScrollbarWidth()\n * @returns {boolean} True if the vertical scrollbar is visible, false otherwise\n */\nexport function hasVerticalScrollbarVisible(scrollbarWidth) {\n if (scrollbarWidth === undefined) scrollbarWidth = getScrollbarWidth()\n return window.innerHeight < document.body.scrollHeight && scrollbarWidth > 0\n}\n\n/**\n * Check if the horizontal scrollbar is visible\n * \n * @param {number} [scrollbarWidth] The width of the scrollbar, defaults to getScrollbarWidth()\n * @returns {boolean} True if the horizontal scrollbar is visible, false otherwise\n */\nexport function hasHorizontalScrollbarVisible(scrollbarWidth) {\n if (scrollbarWidth === undefined) scrollbarWidth = getScrollbarWidth()\n return window.innerWidth < document.body.scrollWidth && scrollbarWidth > 0\n}\n\n/**\n * Disable the scroll on the page.\n * \n * @param {number} [shift=0] If greater than 0 the body will be shifted to the left by the width of the scrollbar, getScrollbarWidth() is used to provide this value \n */\nexport function disableScroll(shift) {\n const body = document.body\n if (shift && hasVerticalScrollbarVisible(shift)) body.style.paddingRight = `${shift}px`\n body.style.overflow = 'hidden'\n}\n\n/**\n * Enable the scroll on the page.\n * \n * @param {boolean} [shift=0] If greater than 0 the body will be shifted back to the left by the width of the scrollbar, getScrollbarWidth() is used to provide this value\n */\nexport function enableScroll(shift) {\n const body = document.body\n body.style.overflow = ''\n if (shift) body.style.paddingRight = ''\n}\n\n/**\n * Parses a string of url query parameters into an object of key value pairs. Converts the values to the correct type.\n * \n * @param {string} [entryQuery] - Optional query string to parse, without the starting ?, defaults to window.location.search without the starting ?\n * @returns {object} of key value pairs\n * @example\n * // url: https://example.com/?test&foo=bar&baz=qux\n * getQueryProperties() // { test: undefined, foo: 'bar', baz: 'qux' }\n */\nexport function getQueryProperties(entryQuery) {\n const query = entryQuery ? entryQuery : window.location.search.replace('?', '')\n if (isEmpty(query)) return {}\n\n return parseUrlParameters(query)\n}\n\n/**\n * Parses a string of url hash parameters into an object of key value pairs. Converts the values to the correct type.\n * \n * @param {string} [entryHash] - Optional hash string to parse, without the starting #, defaults to window.location.hash without the starting #\n * @returns {object} of key value pairs\n * @example\n * // url: https://example.com/#test&foo=bar&baz=qux\n * getHashProperties() // { test: undefined, foo: 'bar', baz: 'qux' }\n */\nexport function getHashProperties(entryHash) {\n const hash = entryHash ? entryHash : window.location.hash.replace('#', '')\n if (isEmpty(hash)) return {}\n\n return parseUrlParameters(hash)\n}\n\nfunction onHashChange(callback) {\n const hash = window.location.hash.replace('#', '')\n if (!isEmpty(hash)) callback(hash)\n}\n\n/**\n * Add a callback function to the hash change event\n * \n * @param {function} callback - The callback function to call when the hash changes\n * @param {string} [single] - Optional string to make sure the listener is initialized only once, defaults to window[single] which is set to true after the first call\n * @example\n * hashChange((hash) => {\n * // Do something with the hash\n * })\n */\nexport function hashChange(callback, single) {\n onHashChange(callback)\n \n if (single && window[single]) return\n if (single) window[single] = true\n \n window.addEventListener('hashchange', () => {\n onHashChange(callback)\n })\n}\n", "import { generateActionButton } from './buttons.js';\nimport { isArray, stringToType, isMobile, parseResolutionString, proportionalParentCoverResize, percentage, fixed } from 'book-of-spells';\n\nexport class SuperVideoBackground {\n constructor(elem, params, id, uid, type, factoryInstance) {\n if (!id) return;\n this.is_mobile = isMobile();\n this.type = type;\n this.id = id;\n this.factoryInstance = factoryInstance;\n\n this.element = elem;\n this.playerElement = null;\n this.uid = uid;\n this.element.setAttribute('data-vbg-uid', uid);\n\n this.buttons = {};\n this.isIntersecting = false;\n\n this.paused = false; // user requested pause. used for blocking intersection softPlay\n this.muted = false;\n this.currentState = 'notstarted';\n\n this.initialPlay = false;\n this.initialVolume = false;\n\n this.volume = 1;\n\n this.params = {};\n\n const DEFAULTS = {\n 'pause': false, //deprecated\n 'play-button': false,\n 'mute-button': false,\n 'autoplay': true,\n 'muted': true,\n 'loop': true,\n 'mobile': true,\n 'load-background': false,\n 'resolution': '16:9',\n 'inline-styles': true,\n 'fit-box': false,\n 'offset': 100, // since showinfo is deprecated and ignored after September 25, 2018. we add +100 to hide it in the overflow\n 'start-at': 0,\n 'end-at': 0,\n 'poster': null,\n 'always-play': false,\n 'volume': 1,\n 'no-cookie': true,\n 'force-on-low-battery': false,\n 'lazyloading': false,\n 'title': 'Video background'\n };\n\n this.params = this.parseProperties(params, DEFAULTS, this.element, ['data-ytbg-', 'data-vbg-']);\n\n //pause deprecated\n if (this.params.pause) {\n this.params['play-button'] = this.params.pause;\n }\n\n this.params.resolution_mod = parseResolutionString(this.params.resolution);\n\n this.muted = this.params.muted;\n\n this.volume = this.params.volume;\n\n this.currentTime = this.params['start-at'] || 0;\n this.duration = this.params['end-at'] || 0;\n this.percentComplete = 0;\n if (this.params['start-at']) this.percentComplete = this.timeToPercentage(this.params['start-at']);\n\n this.buildWrapperHTML();\n\n if (this.is_mobile && !this.params.mobile) return;\n\n if (this.params['play-button']) {\n generateActionButton(this, {\n name: 'playing',\n className: 'play-toggle',\n innerHtml: '',\n initialState: !this.paused,\n stateClassName: 'paused',\n condition_parameter: 'paused',\n stateChildClassNames: ['fa-pause-circle', 'fa-play-circle'],\n actions: ['play', 'pause']\n });\n }\n\n if (this.params['mute-button']) {\n generateActionButton(this, {\n name: 'muted',\n className: 'mute-toggle',\n innerHtml: '',\n initialState: this.muted,\n stateClassName: 'muted',\n condition_parameter: 'muted',\n stateChildClassNames: ['fa-volume-up', 'fa-volume-mute'],\n actions: ['unmute', 'mute']\n });\n }\n }\n\n timeToPercentage(time) {\n if (time <= this.params['start-at']) return 0;\n if (time >= this.duration) return 100;\n if (time <= 0) return 0;\n time -= this.params['start-at']; // normalize\n const duration = this.duration - this.params['start-at']; // normalize\n return percentage(time, duration);\n }\n\n percentageToTime(percentage) {\n if (!this.duration) return this.params['start-at'] || 0;\n if (percentage > 100) return this.duration;\n if (percentage <= 0) return this.params['start-at'] || 0;\n const duration = this.duration - this.params['start-at']; // normalize\n let time = percentage * duration / 100;\n time = fixed(time, 3)\n if (time > duration) time = duration;\n if (this.params['start-at']) time += this.params['start-at']; // normalize\n return time;\n }\n\n resize(element) {\n if (!this.params['fit-box']) proportionalParentCoverResize(element || this.playerElement, this.params.resolution_mod, this.params.offset);\n this.dispatchEvent('video-background-resize');\n }\n\n stylePlayerElement(element) {\n if (!element) return;\n\n if (this.params['inline-styles']) {\n element.style.top = '50%';\n element.style.left = '50%';\n element.style.transform = 'translateX(-50%) translateY(-50%)';\n element.style.position = 'absolute';\n element.style.opacity = 0;\n }\n\n if (this.params['fit-box']) {\n element.style.width = '100%';\n element.style.height = '100%';\n }\n }\n\n buildWrapperHTML() {\n const parent = this.element.parentNode;\n // wrap\n this.element.classList.add('youtube-background', 'video-background');\n \n //set css rules\n const wrapper_styles = {\n \"height\" : \"100%\",\n \"width\" : \"100%\",\n \"z-index\": \"0\",\n \"position\": \"absolute\",\n \"overflow\": \"hidden\",\n \"top\": 0, // added by @insad\n \"left\": 0,\n \"bottom\": 0,\n \"right\": 0\n };\n \n if (!this.params['mute-button']) {\n wrapper_styles[\"pointer-events\"] = \"none\" // avoid right mouse click popup menu\n }\n \n if (this.params['load-background'] || this.params['poster']) {\n this.loadBackground(this.id);\n if (this.params['poster']) wrapper_styles['background-image'] = `url(${ this.params['poster'] })`;\n wrapper_styles['background-size'] = 'cover';\n wrapper_styles['background-repeat'] = 'no-repeat';\n wrapper_styles['background-position'] = 'center';\n }\n \n if (this.params['inline-styles']) {\n for (let property in wrapper_styles) {\n this.element.style[property] = wrapper_styles[property];\n }\n \n if (!['absolute', 'fixed', 'relative', 'sticky'].indexOf(parent.style.position)) {\n parent.style.position = 'relative';\n }\n }\n \n // set play/mute controls wrap\n if (this.params['play-button'] || this.params['mute-button']) {\n const controls = document.createElement('div');\n controls.className = 'video-background-controls';\n \n controls.style.position = 'absolute';\n controls.style.top = '10px';\n controls.style.right = '10px';\n controls.style['z-index'] = 2;\n \n this.controls_element = controls;\n parent.appendChild(controls);\n }\n \n return this.element;\n }\n\n loadBackground(id) {\n if (!this.params['load-background']) return;\n if (!id) return;\n if (this.type === 'youtube') this.element.style['background-image'] = `url(https://img.youtube.com/vi/${id}/hqdefault.jpg)`;\n if (this.type === 'vimeo') this.element.style['background-image'] = `url(https://vumbnail.com/${id}.jpg)`;\n }\n\n destroy() {\n this.playerElement.remove();\n this.element.classList.remove('youtube-background', 'video-background');\n this.element.removeAttribute('data-vbg-uid');\n this.element.style = '';\n\n if (this.params['play-button'] || this.params['mute-button']) {\n this.controls_element.remove();\n }\n\n if (this.timeUpdateTimer) clearInterval(this.timeUpdateTimer);\n this.dispatchEvent('video-background-destroyed');\n }\n\n setDuration(duration) {\n if (this.duration === duration) return;\n\n if (this.params['end-at']) {\n if (duration > this.params['end-at']) {\n this.duration = this.params['end-at'];\n return;\n }\n if (duration < this.params['end-at']) {\n this.duration = duration;\n return;\n }\n } else {\n this.duration = duration;\n return;\n }\n\n if (duration <= 0) this.duration = this.params['end-at'];\n }\n\n setStartAt(startAt) {\n this.params['start-at'] = startAt;\n }\n\n setEndAt(endAt) {\n this.params['end-at'] = endAt;\n if (this.duration > endAt) this.duration = endAt;\n if (this.currentTime > endAt) this.onVideoEnded();\n }\n\n dispatchEvent(name) {\n this.element.dispatchEvent(new CustomEvent(name, { bubbles: true, detail: this }));\n }\n\n shouldPlay() {\n if (this.currentState === 'ended' && !this.params.loop) return false;\n if (this.params['always-play'] && this.currentState !== 'playing') return true;\n if (this.isIntersecting && this.params.autoplay && this.currentState !== 'playing') return true;\n return false;\n }\n\n mobileLowBatteryAutoplayHack() {\n if (!this.params['force-on-low-battery']) return;\n if (!this.is_mobile && this.params.mobile) return;\n\n const forceAutoplay = function() {\n if (!this.initialPlay && this.params.autoplay && this.params.muted) {\n this.softPlay();\n\n if (!this.isIntersecting && !this.params['always-play']) {\n this.softPause();\n }\n }\n }\n \n document.addEventListener('touchstart', forceAutoplay.bind(this), { once: true });\n }\n\n parseProperties(params, defaults, element, attr_prefix) {\n let res_params = {};\n \n if (!params) {\n res_params = defaults;\n } else {\n for (let k in defaults) {\n //load in defaults if the param hasn't been set\n res_params[k] = !params.hasOwnProperty(k) ? defaults[k] : params[k];\n }\n }\n \n if (!element) return res_params;\n // load params from data attributes\n for (let k in res_params) {\n let data;\n \n if (isArray(attr_prefix)) {\n for (let i = 0; i < attr_prefix.length; i++) {\n const temp_data = element.getAttribute(attr_prefix[i]+k);\n if (temp_data) {\n data = temp_data;\n break;\n }\n }\n } else {\n data = element.getAttribute(attr_prefix+k);\n }\n \n if (data !== undefined && data !== null) {\n res_params[k] = stringToType(data);\n }\n }\n \n return res_params;\n }\n}\n", "import { SuperVideoBackground } from './super-video-background.js';\nimport { RE_YOUTUBE } from 'book-of-spells';\n\nexport class YoutubeBackground extends SuperVideoBackground {\n constructor(elem, params, id, uid, factoryInstance) {\n super(elem, params, id, uid, 'youtube', factoryInstance);\n\n if (!id) return;\n if (this.is_mobile && !this.params.mobile) return;\n this.injectScript();\n\n this.player = null;\n\n this.injectPlayer();\n\n this.STATES = {\n '-1': 'notstarted',\n '0': 'ended',\n '1': 'playing',\n '2': 'paused',\n '3': 'buffering',\n '5': 'cued'\n };\n\n this.timeUpdateTimer = null;\n this.timeUpdateInterval = 250;\n\n this.initYTPlayer();\n }\n\n startTimeUpdateTimer() {\n if (this.timeUpdateTimer) return;\n this.timeUpdateTimer = setInterval(this.onVideoTimeUpdate.bind(this), this.timeUpdateInterval);\n };\n\n stopTimeUpdateTimer() {\n clearInterval(this.timeUpdateTimer);\n this.timeUpdateTimer = null;\n };\n\n convertState(state) {\n return this.STATES[state];\n }\n\n initYTPlayer() {\n if (!window.hasOwnProperty('YT') || this.player !== null) return;\n\n this.player = new YT.Player(this.uid, {\n events: {\n 'onReady': this.onVideoPlayerReady.bind(this),\n 'onStateChange': this.onVideoStateChange.bind(this),\n // 'onError': this.onVideoError.bind(this)\n }\n });\n\n if (this.volume !== 1 && !this.muted) this.setVolume(this.volume);\n }\n\n onVideoError(event) {\n console.error(event);\n }\n\n injectScript() {\n const src = 'https://www.youtube.com/player_api';\n if (window.hasOwnProperty('YT') || document.querySelector(`script[src=\"${src}\"]`)) return\n const tag = document.createElement('script');\n tag.async = true;\n tag.src = src;\n const firstScriptTag = document.getElementsByTagName('script')[0];\n firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);\n }\n\n generatePlayerElement() {\n const playerElement = document.createElement('iframe');\n if (this.params.title) playerElement.setAttribute('title', this.params.title);\n playerElement.setAttribute('frameborder', 0);\n playerElement.setAttribute('allow', 'autoplay; mute');\n if (this.params['lazyloading']) playerElement.setAttribute('loading', 'lazy');\n\n return playerElement;\n }\n\n generateSrcURL(id) {\n let site = 'https://www.youtube.com/embed/';\n if (this.params['no-cookie']) {\n site = 'https://www.youtube-nocookie.com/embed/';\n }\n let src = `${site}${id}?&enablejsapi=1&disablekb=1&controls=0&rel=0&iv_load_policy=3&cc_load_policy=0&playsinline=1&showinfo=0&modestbranding=1&fs=0`;\n\n if (this.params.muted) {\n src += '&mute=1';\n }\n \n if (this.params.autoplay && (this.params['always-play'] || this.isIntersecting)) {\n src += '&autoplay=1';\n }\n \n if (this.params.loop) {\n src += '&loop=1';\n }\n\n return src;\n }\n\n injectPlayer() {\n this.playerElement = this.generatePlayerElement();\n this.src = this.generateSrcURL(this.id);\n this.playerElement.src = this.src;\n this.playerElement.id = this.uid;\n\n this.stylePlayerElement(this.playerElement);\n this.element.appendChild(this.playerElement);\n this.resize(this.playerElement);\n }\n\n /* ===== API ===== */\n\n setSource(url) {\n const pts = url.match(RE_YOUTUBE);\n if (!pts || !pts.length) return;\n\n this.id = pts[1];\n this.src = this.generateSrcURL(this.id);\n this.playerElement.src = this.src;\n\n if (this.element.hasAttribute('data-vbg')) this.element.setAttribute('data-vbg', this.src);\n if (this.element.hasAttribute('data-ytbg')) this.element.setAttribute('data-ytbg', this.src);\n this.loadBackground(this.id);\n }\n\n onVideoTimeUpdate() {\n const ctime = this.player.getCurrentTime();\n if (ctime === this.currentTime) return;\n this.currentTime = ctime;\n this.percentComplete = this.timeToPercentage(this.currentTime);\n if (this.params['end-at'] && this.duration && this.currentTime >= this.duration) {\n this.currentState = 'ended';\n this.dispatchEvent('video-background-state-change');\n this.onVideoEnded();\n this.stopTimeUpdateTimer();\n return;\n }\n this.dispatchEvent('video-background-time-update');\n }\n\n onVideoPlayerReady() {\n this.mobileLowBatteryAutoplayHack();\n\n if (this.params.autoplay && (this.params['always-play'] || this.isIntersecting)) {\n if (this.params['start-at']) this.seekTo(this.params['start-at']);\n this.player.playVideo();\n }\n\n this.setDuration(this.player.getDuration());\n\n this.dispatchEvent('video-background-ready');\n }\n\n onVideoStateChange(event) {\n this.currentState = this.convertState(event.data);\n\n if (this.currentState === 'ended') this.onVideoEnded();\n \n if (this.currentState === 'notstarted' && this.params.autoplay) {\n this.seekTo(this.params['start-at']);\n this.player.playVideo();\n }\n\n if (this.currentState === 'playing') this.onVideoPlay();\n \n if (this.currentState === 'paused') this.onVideoPause();\n\n this.dispatchEvent('video-background-state-change');\n }\n\n onVideoPlay() {\n if (!this.initialPlay) {\n this.initialPlay = true;\n this.playerElement.style.opacity = 1;\n }\n\n const seconds = this.player.getCurrentTime();\n if (this.params['start-at'] && seconds < this.params['start-at'] ) {\n this.seekTo(this.params['start-at']);\n }\n\n if (this.duration && seconds >= this.duration) {\n this.seekTo(this.params['start-at']);\n }\n\n if (!this.duration) {\n this.setDuration(this.player.getDuration());\n }\n\n this.dispatchEvent('video-background-play');\n this.startTimeUpdateTimer();\n }\n\n onVideoPause() {\n this.stopTimeUpdateTimer();\n this.dispatchEvent('video-background-pause');\n }\n\n onVideoEnded() {\n this.dispatchEvent('video-background-ended');\n\n if (!this.params.loop) return this.pause();\n this.seekTo(this.params['start-at']);\n this.player.playVideo();\n }\n\n seek(percentage) {\n this.seekTo(this.percentageToTime(percentage), true);\n }\n\n seekTo(seconds, allowSeekAhead = true) {\n if (!this.player) return;\n this.player.seekTo(seconds, allowSeekAhead);\n this.dispatchEvent('video-background-seeked');\n }\n\n softPause() {\n if (!this.player || this.currentState === 'paused') return;\n this.stopTimeUpdateTimer();\n this.player.pauseVideo();\n }\n\n softPlay() {\n if (!this.player || this.currentState === 'playing') return;\n this.player.playVideo();\n }\n\n play() {\n if (!this.player) return;\n this.paused = false;\n \n this.player.playVideo();\n }\n\n pause() {\n if (!this.player) return;\n this.paused = true;\n this.stopTimeUpdateTimer();\n this.player.pauseVideo();\n }\n\n unmute() {\n if (!this.player) return;\n this.muted = false;\n \n if (!this.initialVolume) {\n this.initialVolume = true;\n this.setVolume(this.params.volume);\n }\n this.player.unMute();\n this.dispatchEvent('video-background-unmute');\n }\n\n mute() {\n if (!this.player) return;\n this.muted = true;\n \n this.player.mute();\n this.dispatchEvent('video-background-mute');\n }\n\n getVolume() {\n if (!this.player) return;\n return this.player.getVolume() / 100;\n }\n\n setVolume(volume) {\n if (!this.player) return;\n this.volume = volume;\n \n this.player.setVolume(volume * 100);\n this.dispatchEvent('video-background-volume-change');\n }\n}\n ", "import { SuperVideoBackground } from './super-video-background.js';\nimport { RE_VIMEO } from 'book-of-spells';\n\nexport class VimeoBackground extends SuperVideoBackground {\n constructor(elem, params, id, uid, factoryInstance) {\n super(elem, params, id.id, uid, 'vimeo', factoryInstance);\n if (!id) return;\n this.unlisted = id.unlisted;\n\n if (this.is_mobile && !this.params.mobile) return;\n this.injectScript();\n\n this.player = null;\n\n this.injectPlayer();\n\n this.initVimeoPlayer();\n }\n\n injectScript() {\n const src = 'https://player.vimeo.com/api/player.js';\n if (window.hasOwnProperty('Vimeo') || document.querySelector(`script[src=\"${src}\"]`)) return;\n const tag = document.createElement('script');\n tag.async = true;\n if (window.hasOwnProperty('onVimeoIframeAPIReady') && typeof window.onVimeoIframeAPIReady === 'function') tag.addEventListener('load', () => {\n window.onVimeoIframeAPIReady();\n });\n tag.src = src;\n const firstScriptTag = document.getElementsByTagName('script')[0];\n firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);\n }\n\n initVimeoPlayer() {\n if (!window.hasOwnProperty('Vimeo') || this.player !== null) return;\n this.player = new Vimeo.Player(this.playerElement);\n \n this.player.on('loaded', this.onVideoPlayerReady.bind(this));\n this.player.on('ended', this.onVideoEnded.bind(this));\n this.player.on('play', this.onVideoPlay.bind(this));\n this.player.on('pause', this.onVideoPause.bind(this));\n this.player.on('bufferstart', this.onVideoBuffering.bind(this));\n this.player.on('timeupdate', this.onVideoTimeUpdate.bind(this));\n // this.player.on('error', this.onVideoError.bind(this));\n\n if (this.volume !== 1 && !this.muted) this.setVolume(this.volume);\n }\n\n onVideoError(event) {\n console.error(event);\n }\n\n generatePlayerElement() {\n const playerElement = document.createElement('iframe');\n if (this.params.title) playerElement.setAttribute('title', this.params.title);\n playerElement.setAttribute('frameborder', 0);\n playerElement.setAttribute('allow', 'autoplay; mute');\n if (this.params['lazyloading']) playerElement.setAttribute('loading', 'lazy');\n\n return playerElement;\n }\n\n generateSrcURL(id, unlisted) {\n unlisted = unlisted ? `h=${unlisted}&` : ''\n let src = `https://player.vimeo.com/video/${id}?${unlisted}background=1&controls=0`;\n \n if (this.params.muted) {\n src += '&muted=1';\n }\n \n if (this.params.autoplay && (this.params['always-play'] || this.isIntersecting)) {\n src += '&autoplay=1';\n }\n\n if (this.params.loop) {\n src += '&loop=1&autopause=0';\n }\n \n if (this.params['no-cookie']) {\n src += '&dnt=1';\n }\n \n //WARN\u2757\uFE0F: this is a hash not a query param\n if (this.params['start-at']) {\n src += '#t=' + this.params['start-at'] + 's';\n }\n\n return src;\n }\n\n injectPlayer() {\n this.playerElement = this.generatePlayerElement();\n this.src = this.generateSrcURL(this.id, this.unlisted);\n this.playerElement.src = this.src;\n this.playerElement.id = this.uid;\n \n this.stylePlayerElement(this.playerElement);\n this.element.appendChild(this.playerElement);\n this.resize(this.playerElement);\n }\n\n updateState(state) {\n this.currentState = state;\n this.dispatchEvent('video-background-state-change');\n }\n\n /* ===== API ===== */\n\n setSource(url) {\n const pts = url.match(RE_VIMEO);\n if (!pts || !pts.length) return;\n\n this.id = pts[1];\n this.src = this.generateSrcURL(this.id);\n this.playerElement.src = this.src;\n\n if (this.element.hasAttribute('data-vbg')) this.element.setAttribute('data-vbg', this.src);\n if (this.element.hasAttribute('data-ytbg')) this.element.setAttribute('data-ytbg', this.src);\n this.loadBackground(this.id);\n }\n\n onVideoPlayerReady() {\n this.mobileLowBatteryAutoplayHack();\n\n if (this.params['start-at']) this.seekTo(this.params['start-at']);\n\n if (this.params.autoplay && (this.params['always-play'] || this.isIntersecting)) {\n this.player.play();\n }\n\n this.player.getDuration().then((duration) => {\n this.setDuration(duration);\n });\n\n this.dispatchEvent('video-background-ready');\n }\n\n onVideoEnded() {\n this.updateState('ended');\n this.dispatchEvent('video-background-ended');\n if (!this.params.loop) return this.pause();\n \n this.seekTo(this.params['start-at']);\n this.updateState('playing');\n this.dispatchEvent('video-background-play');\n }\n\n onVideoTimeUpdate(event) {\n this.currentTime = event.seconds;\n this.percentComplete = this.timeToPercentage(event.seconds);\n this.dispatchEvent('video-background-time-update');\n this.setDuration(event.duration);\n\n if (this.params['end-at'] && this.duration && event.seconds >= this.duration) {\n this.onVideoEnded();\n }\n }\n\n onVideoBuffering() {\n this.updateState('buffering');\n }\n\n onVideoPlay(event) {\n this.setDuration(event.duration);\n\n if (!this.initialPlay) {\n this.initialPlay = true;\n this.playerElement.style.opacity = 1;\n\n // gotta set loop manually, cause for some reason it's true by default\n this.player.setLoop(this.params.loop);\n\n //Hotfixing an issue that it automatically starts playing after buffering on the first load, sometimes, not always, for an unknown reason\n if (!(this.params.autoplay && (this.params['always-play'] || this.isIntersecting))) {\n return this.player.pause();\n }\n }\n\n const seconds = event.seconds;\n if (this.params['start-at'] && seconds < this.params['start-at']) {\n this.seekTo(this.params['start-at']);\n }\n\n if (this.duration && seconds >= this.duration) {\n this.seekTo(this.params['start-at']);\n }\n\n this.updateState('playing');\n this.dispatchEvent('video-background-play');\n }\n\n onVideoPause() {\n this.updateState('paused');\n this.dispatchEvent('video-background-pause');\n }\n\n seek(percentage) {\n this.seekTo(this.percentageToTime(percentage));\n }\n\n seekTo(time) {\n if (!this.player) return;\n this.player.setCurrentTime(time);\n this.dispatchEvent('video-background-seeked');\n }\n\n softPause() {\n if (!this.player || this.currentState === 'paused') return;\n this.player.pause();\n }\n\n softPlay() {\n if (!this.player || this.currentState === 'playing') return;\n this.player.play();\n }\n\n play() {\n if (!this.player) return;\n this.paused = false;\n \n this.player.play();\n }\n\n pause() {\n if (!this.player) return;\n this.paused = true;\n \n this.player.pause();\n }\n\n unmute() {\n if (!this.player) return;\n this.muted = false;\n \n if (!this.initialVolume) {\n this.initialVolume = true;\n this.setVolume(this.params.volume);\n }\n this.player.setMuted(false);\n this.dispatchEvent('video-background-unmute');\n }\n\n mute() {\n if (!this.player) return;\n this.muted = true;\n \n this.player.setMuted(true);\n this.dispatchEvent('video-background-mute');\n }\n\n getVolume() {\n if (!this.player) return;\n return this.player.getVolume();\n }\n\n setVolume(volume) {\n if (!this.player) return;\n this.volume = volume;\n \n this.player.setVolume(volume);\n this.dispatchEvent('video-background-volume-change');\n }\n}\n", "import { SuperVideoBackground } from './super-video-background.js';\nimport { RE_VIDEO } from 'book-of-spells';\n\nexport class VideoBackground extends SuperVideoBackground {\n constructor(elem, params, vid_data, uid, factoryInstance) {\n super(elem, params, vid_data.link, uid, 'video', factoryInstance);\n if (!vid_data || !vid_data.link) return;\n if (this.is_mobile && !this.params.mobile) return;\n\n this.src = vid_data.link;\n this.ext = /(?:\\.([^.]+))?$/.exec(vid_data.id)[1];\n this.uid = uid;\n this.element.setAttribute('data-vbg-uid', uid);\n this.player = null;\n this.buttons = {};\n\n this.MIME_MAP = {\n 'ogv' : 'video/ogg',\n 'ogm' : 'video/ogg',\n 'ogg' : 'video/ogg',\n 'avi' : 'video/avi',\n 'mp4' : 'video/mp4',\n 'webm' : 'video/webm',\n 'm4v' : 'video/x-m4v',\n 'mov' : 'video/quicktime',\n 'qt' : 'video/quicktime',\n };\n\n this.mime = this.MIME_MAP[this.ext.toLowerCase()];\n\n this.injectPlayer();\n\n this.mobileLowBatteryAutoplayHack();\n this.dispatchEvent('video-background-ready');\n }\n\n generatePlayerElement() {\n const playerElement = document.createElement('video');\n if (this.params.title) playerElement.setAttribute('title', this.params.title);\n playerElement.setAttribute('playsinline', '');\n if (this.params.loop) playerElement.setAttribute('loop', '');\n if (this.params.autoplay && (this.params['always-play'] || this.isIntersecting)) {\n playerElement.setAttribute('autoplay', '');\n playerElement.autoplay = true;\n }\n if (this.muted) {\n playerElement.setAttribute('muted', '');\n playerElement.muted = true;\n }\n if (this.params['lazyloading']) playerElement.setAttribute('loading', 'lazy');\n\n return playerElement;\n }\n\n injectPlayer() {\n this.player = this.generatePlayerElement();\n this.playerElement = this.player;\n \n if (this.volume !== 1 && !this.muted) this.setVolume(this.volume);\n \n this.playerElement.setAttribute('id', this.uid)\n \n this.stylePlayerElement(this.playerElement);\n\n this.player.addEventListener('loadedmetadata', this.onVideoLoadedMetadata.bind(this));\n this.player.addEventListener('durationchange', this.onVideoLoadedMetadata.bind(this));\n this.player.addEventListener('canplay', this.onVideoCanPlay.bind(this));\n this.player.addEventListener('timeupdate', this.onVideoTimeUpdate.bind(this));\n this.player.addEventListener('play', this.onVideoPlay.bind(this));\n this.player.addEventListener('pause', this.onVideoPause.bind(this));\n this.player.addEventListener('waiting', this.onVideoBuffering.bind(this));\n this.player.addEventListener('ended', this.onVideoEnded.bind(this));\n\n this.element.appendChild(this.playerElement);\n const source = document.createElement('source');\n source.setAttribute('src', this.src);\n source.setAttribute('type', this.mime);\n this.playerElement.appendChild(source);\n this.resize(this.playerElement);\n }\n\n updateState(state) {\n this.currentState = state;\n this.dispatchEvent('video-background-state-change');\n }\n\n /* ===== API ===== */\n\n setSource(url) {\n const pts = url.match(RE_VIDEO);\n if (!pts || !pts.length) return;\n this.id = pts[1];\n this.ext = /(?:\\.([^.]+))?$/.exec(this.id)[1];\n this.mime = this.MIME_MAP[this.ext.toLowerCase()];\n this.playerElement.innerHTML = '';\n const source = document.createElement('source');\n source.setAttribute('src', url);\n source.setAttribute('type', this.mime);\n this.playerElement.appendChild(source);\n this.src = url;\n\n if (this.element.hasAttribute('data-vbg')) this.element.setAttribute('data-vbg', this.src);\n if (this.element.hasAttribute('data-ytbg')) this.element.setAttribute('data-ytbg', this.src);\n }\n\n onVideoLoadedMetadata() {\n this.setDuration(this.player.duration);\n }\n\n onVideoCanPlay() {\n this.setDuration(this.player.duration);\n }\n\n onVideoTimeUpdate() {\n this.currentTime = this.player.currentTime;\n this.percentComplete = this.timeToPercentage(this.player.currentTime);\n this.dispatchEvent('video-background-time-update');\n\n if (this.params['end-at'] && this.currentTime >= this.duration) {\n this.onVideoEnded();\n }\n }\n\n onVideoPlay() {\n if (!this.initialPlay) {\n this.initialPlay = true;\n this.playerElement.style.opacity = 1;\n }\n \n const seconds = this.player.currentTime;\n if (this.params['start-at'] && seconds <= this.params['start-at']) {\n this.seekTo(this.params['start-at']);\n }\n\n if (this.duration && seconds >= this.duration) {\n this.seekTo(this.params['start-at']);\n }\n\n this.updateState('playing');\n this.dispatchEvent('video-background-play');\n }\n\n onVideoPause() {\n this.updateState('paused');\n this.dispatchEvent('video-background-pause');\n }\n\n onVideoEnded() {\n this.updateState('ended');\n this.dispatchEvent('video-background-ended');\n if (!this.params.loop) return this.pause();\n \n this.seekTo(this.params['start-at']);\n this.onVideoPlay();\n }\n\n onVideoBuffering() {\n this.updateState('buffering');\n }\n\n seek(percentage) {\n this.seekTo(this.percentageToTime(percentage));\n }\n\n seekTo(seconds) {\n if (!this.player) return;\n if (this.player.hasOwnProperty('fastSeek')) {\n this.player.fastSeek(seconds);\n return;\n }\n this.player.currentTime = seconds;\n this.dispatchEvent('video-background-seeked');\n }\n\n softPause() {\n if (!this.player || this.currentState === 'paused') return;\n this.player.pause();\n }\n\n softPlay() {\n if (!this.player || this.currentState === 'playing') return;\n this.player.play();\n }\n\n play() {\n if (!this.player) return;\n this.paused = false;\n\n this.player.play();\n }\n\n pause() {\n if (!this.player) return;\n this.paused = true;\n \n this.player.pause();\n }\n\n unmute() {\n if (!this.player) return;\n this.muted = false;\n \n this.player.muted = false;\n if (!this.initialVolume) {\n this.initialVolume = true;\n this.setVolume(this.params.volume);\n }\n this.dispatchEvent('video-background-unmute');\n }\n\n mute() {\n if (!this.player) return;\n this.muted = true;\n \n this.player.muted = true;\n this.dispatchEvent('video-background-mute');\n }\n\n getVolume() {\n if (!this.player) return;\n return this.player.volume;\n }\n\n setVolume(volume) {\n if (!this.player) return;\n this.volume = volume;\n \n this.player.volume = volume;\n this.dispatchEvent('video-background-volume-change');\n }\n}\n", "import { YoutubeBackground } from './lib/youtube-background.js';\nimport { VimeoBackground } from './lib/vimeo-background.js';\nimport { VideoBackground } from './lib/video-background.js';\n\nimport { randomIntInclusive, RE_VIMEO, RE_YOUTUBE, RE_VIDEO } from 'book-of-spells';\n\nexport class VideoBackgrounds {\n constructor(selector, params) {\n this.elements = selector;\n if (this.elements instanceof Element) this.elements = [this.elements];\n if (typeof this.elements === 'string') this.elements = document.querySelectorAll(selector);\n\n this.index = {};\n\n const self = this;\n\n this.intersectionObserver = null;\n\n if ('IntersectionObserver' in window) {\n this.intersectionObserver = new IntersectionObserver(function (entries) {\n entries.forEach(function (entry) {\n const uid = entry.target.getAttribute('data-vbg-uid');\n \n if (uid && self.index.hasOwnProperty(uid) && entry.isIntersecting) {\n self.index[uid].isIntersecting = true;\n try {\n if (self.index[uid].player && !self.index[uid].paused) self.index[uid].softPlay();\n } catch (e) {\n // console.log(e);\n }\n } else {\n self.index[uid].isIntersecting = false;\n try {\n if (self.index[uid].player) self.index[uid].softPause();\n } catch (e) {\n // console.log(e);\n }\n }\n });\n });\n }\n\n this.resizeObserver = null;\n\n if ('ResizeObserver' in window) {\n this.resizeObserver = new ResizeObserver(function (entries) {\n entries.forEach(function (entry) {\n const uid = entry.target.getAttribute('data-vbg-uid');\n\n if (uid && self.index.hasOwnProperty(uid)) {\n window.requestAnimationFrame(() => self.index[uid].resize());\n }\n });\n });\n } else {\n window.addEventListener('resize', function () {\n for (let k in self.index) {\n window.requestAnimationFrame(() => self.index[k].resize(self.index[k].playerElement));\n }\n });\n }\n \n this.initPlayers();\n\n if (!this.elements || !this.elements.length) return;\n for (let i = 0; i < this.elements.length; i++) {\n const element = this.elements[i];\n this.add(element, params);\n }\n\n document.addEventListener('visibilitychange', this.onVisibilityChange.bind(this));\n }\n\n onVisibilityChange() {\n if (document.hidden) return;\n\n for (let k in this.index) {\n const instance = this.index[k];\n if (instance.shouldPlay()) {\n instance.softPlay();\n }\n }\n }\n\n add(element, params) {\n if (!element) return;\n if (element.hasAttribute('data-vbg-uid')) return;\n\n if (!this.intersectionObserver) {\n if (!params) params = {};\n params['always-play'] = true;\n }\n\n const link = element.getAttribute('data-youtube') || element.getAttribute('data-vbg');\n const vid_data = this.getVidID(link);\n \n if (!vid_data) return;\n \n const uid = this.generateUID(vid_data.id);\n \n if (!uid) return;\n \n switch (vid_data.type) {\n case 'YOUTUBE':\n const yb = new YoutubeBackground(element, params, vid_data.id, uid, this);\n this.index[uid] = yb;\n break;\n case 'VIMEO':\n const vm = new VimeoBackground(element, params, vid_data, uid, this);\n this.index[uid] = vm;\n break;\n case 'VIDEO':\n const vid = new VideoBackground(element, params, vid_data, uid, this);\n this.index[uid] = vid;\n break;\n }\n\n if (this.resizeObserver) {\n this.resizeObserver.observe(element);\n }\n \n if (!this.index[uid].params['always-play'] && this.intersectionObserver) {\n this.intersectionObserver.observe(element);\n }\n }\n\n destroy(element) {\n const uid = element.uid || element.getAttribute('data-vbg-uid');\n if (uid && this.index.hasOwnProperty(uid)) {\n if (!this.index[uid].params['always-play'] && this.intersectionObserver) this.intersectionObserver.unobserve(element);\n if (this.resizeObserver) this.resizeObserver.unobserve(element);\n this.index[uid].destroy();\n delete this.index[uid];\n }\n }\n\n destroyAll() {\n for (let k in this.index) {\n this.destroy(this.index[k].playerElement);\n }\n }\n\n getVidID(link) {\n if (link === undefined && link === null) return;\n\n this.re = {};\n this.re.YOUTUBE = RE_YOUTUBE;\n this.re.VIMEO = RE_VIMEO;\n this.re.VIDEO = RE_VIDEO;\n \n for (let k in this.re) {\n const pts = link.match(this.re[k]);\n\n if (pts && pts.length) {\n this.re[k].lastIndex = 0;\n const data = {\n id: pts[1],\n type: k,\n regex_pts: pts,\n link: link\n };\n \n if (k === 'VIMEO') {\n const unlistedQueryRegex = /(\\?|&)h=([^=&#?]+)/;\n const unlistedPathRegex = /\\/[^\\/\\:\\.]+(\\:|\\/)([^:?\\/]+)\\s?$/;\n const unlistedQuery = link.match(unlistedPathRegex) || link.match(unlistedQueryRegex);\n if (unlistedQuery) data.unlisted = unlistedQuery[2];\n }\n\n return data;\n }\n }\n \n return;\n }\n\n generateUID(pref) {\n //index the instance\n pref = pref.replace(/[^a-zA-Z0-9\\-_]/g, '-'); //sanitize id\n pref = pref.replace(/-{2,}/g, '-'); //remove double dashes\n pref = pref.replace(/^-+/, '').replace(/-+$/, ''); //trim dashes\n pref = 'vbg-'+ pref; //prefix id with 'vbg-\n\n let uid = pref +'-'+ randomIntInclusive(0, 9999);\n while (this.index.hasOwnProperty(uid)) {\n uid = pref +'-'+ randomIntInclusive(0, 9999);\n }\n \n return uid;\n }\n\n get(element) {\n const uid = typeof element === 'string' ? element : element.getAttribute('data-vbg-uid');\n if (uid && this.index.hasOwnProperty(uid)) return this.index[uid];\n }\n\n pauseAll() {\n for (let k in this.index) {\n this.index[k].pause();\n }\n }\n\n playAll() {\n for (let k in this.index) {\n this.index[k].play();\n }\n }\n\n muteAll() {\n for (let k in this.index) {\n this.index[k].mute();\n }\n }\n\n unmuteAll() {\n for (let k in this.index) {\n this.index[k].unmute();\n }\n }\n\n setVolumeAll(volume) {\n for (let k in this.index) {\n this.index[k].setVolume(volume);\n }\n }\n\n initPlayers(callback) {\n const self = this;\n \n window.onYouTubeIframeAPIReady = function () {\n for (let k in self.index) {\n if (self.index[k] instanceof YoutubeBackground) {\n self.index[k].initYTPlayer();\n }\n }\n \n if (callback) {\n setTimeout(callback, 100);\n }\n };\n \n if (window.hasOwnProperty('YT') && window.YT.loaded) {\n window.onYouTubeIframeAPIReady();\n }\n \n window.onVimeoIframeAPIReady = function () {\n for (let k in self.index) {\n if (self.index[k] instanceof VimeoBackground) {\n self.index[k].initVimeoPlayer();\n }\n }\n \n if (callback) {\n setTimeout(callback, 100);\n }\n }\n \n if (window.hasOwnProperty('Vimeo') && window.Vimeo.hasOwnProperty('Player')) {\n window.onVimeoIframeAPIReady();\n }\n }\n}\n", "import { VideoBackgrounds } from './video-backgrounds.js';\n\nif (typeof jQuery == 'function') {\n (function ($) {\n $.fn.youtube_background = function (params) {\n const $this = $(this);\n if (window.hasOwnProperty('VIDEO_BACKGROUNDS')) {\n window.VIDEO_BACKGROUNDS.add($this, params);\n return $this;\n }\n window.VIDEO_BACKGROUNDS = new VideoBackgrounds(this, params);\n return $this;\n };\n })(jQuery);\n}\n\nwindow.VideoBackgrounds = VideoBackgrounds;\n"], + "mappings": "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", + "names": ["percentage", "percentage", "percentage", "percentage"] + } +diff --git a/node_modules/youtube-background/jquery.youtube-background.min.js b/node_modules/youtube-background/jquery.youtube-background.min.js +index 1e7f15d..edbfd2f 100644 +--- a/node_modules/youtube-background/jquery.youtube-background.min.js ++++ b/node_modules/youtube-background/jquery.youtube-background.min.js +@@ -1,2 +1,2 @@ + /* youtube-background v1.1.8 | https://github.com/stamat/youtube-background | MIT License */ +-(()=>{function l(t){t&&(t.element.classList.add(t.stateClassName),t.element.firstChild.classList.remove(t.stateChildClassNames[0]),t.element.firstChild.classList.add(t.stateChildClassNames[1]),t.element.setAttribute("aria-pressed",!1))}function E(t){t&&(t.element.classList.remove(t.stateClassName),t.element.firstChild.classList.add(t.stateChildClassNames[0]),t.element.firstChild.classList.remove(t.stateChildClassNames[1]),t.element.setAttribute("aria-pressed",!0))}function u(t,e){const i=document.createElement("button");i.className=e.className,i.innerHTML=e.innerHtml,i.setAttribute("role","switch"),i.firstChild.classList.add(e.stateChildClassNames[0]),i.setAttribute("aria-pressed",!e.initialState),e.element=i,t.params[e.condition_parameter]===e.initialState&&l(e),i.addEventListener("click",function(s){this.classList.contains(e.stateClassName)?(E(e),t[e.actions[0]]()):(l(e),t[e.actions[1]]())}),t.buttons[e.name]={element:i,button_properties:e},t.controls_element.appendChild(i)}function w(t){if(/^\s*(true|false)\s*$/i.test(t))return t==="true"}function k(t){if(/^\s*\d+\s*$/.test(t))return parseInt(t);if(/^\s*[\d.]+\s*$/.test(t))return parseFloat(t)}function T(t){if(/^\s*\[.*\]\s*$/.test(t))try{return JSON.parse(t)}catch{}}function V(t){if(/^\s*\{.*\}\s*$/.test(t))try{return JSON.parse(t)}catch{}}function P(t){if(/^\s*\/.*\/g?i?\s*$/.test(t))try{return new RegExp(t)}catch{}}function A(t){if(/^\s*null\s*$/.test(t))return null;const e=w(t);return e!==void 0?e:k(t)||T(t)||V(t)||P(t)||t}function S(t){return Array.isArray(t)}function x(t){return typeof t=="string"}function d(t,e,i=!1){if(t=Number(t),e=Number(e),isNaN(t)||isNaN(e))throw new TypeError("Both min and max must be numbers");if(t>e&&([t,e]=[e,t]),t===e)return t;t=Math.round(t),e=Math.round(e);const s=i?N():Math.random();return Math.floor(s*(e-t+1))+t}function I(t,e){return e?parseFloat(t.toFixed(e)):parseInt(t)}function C(t,e){return!t||!e||Number.isNaN(t)||Number.isNaN(e)?0:t/e*100}function N(){if(!crypto)return Math.random();if(crypto.getRandomValues)return crypto.getRandomValues(new Uint32Array(1))[0]/4294967295}var p=/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/i,m=/(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|video\/|)(\d+)(?:[a-zA-Z0-9_\-]+)?/i,c=/\/([^\/]+\.(?:mp4|ogg|ogv|ogm|webm|avi))\s*$/i;function L(t){const e=1.7777777778;if(!t||!t.length||/16[\:x\-\/]{1}9/i.test(t))return e;const i=t.split(/\s?[\:x\-\/]{1}\s?/i);if(i.length<2)return e;const s=parseInt(i[0]),a=parseInt(i[1]);return s===0||a===0||isNaN(s)||isNaN(a)?e:s/a}function y(t,e=document){if(t instanceof Array||t instanceof NodeList)return t;if(t instanceof Element)return[t];if(e instanceof Element||e instanceof Document)return e.querySelectorAll(t);if(x(e)&&(e=y(e)),!e instanceof Array&&!e instanceof NodeList)return[];const i=[];for(const s of e)i.push(...s.querySelectorAll(t));return i}function O(t,e=1,i=0){t instanceof Element&&(t=[t]),typeof t=="string"&&(t=y(t));for(const s of t){const a=s.parentNode.offsetHeight+i,r=s.parentNode.offsetWidth+i;e>r/a?(s.style.width=a*e+"px",s.style.height=a+"px"):(s.style.width=r+"px",s.style.height=r/e+"px")}}function U(t){return/\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(t)||/\b(Android|Windows Phone|iPad|iPod)\b/i.test(t)}function R(){return"maxTouchPoints"in navigator?navigator.maxTouchPoints>0:"matchMedia"in window?!!matchMedia("(pointer:coarse)").matches:"orientation"in window?!0:U(navigator.userAgent)}var h=class{constructor(t,e,i,s,a,r){if(!i)return;this.is_mobile=R(),this.type=a,this.id=i,this.factoryInstance=r,this.element=t,this.playerElement=null,this.uid=s,this.element.setAttribute("data-vbg-uid",s),this.buttons={},this.isIntersecting=!1,this.paused=!1,this.muted=!1,this.currentState="notstarted",this.initialPlay=!1,this.initialVolume=!1,this.volume=1,this.params={};const n={pause:!1,"play-button":!1,"mute-button":!1,autoplay:!0,muted:!0,loop:!0,mobile:!0,"load-background":!1,resolution:"16:9","inline-styles":!0,"fit-box":!1,offset:100,"start-at":0,"end-at":0,poster:null,"always-play":!1,volume:1,"no-cookie":!0,"force-on-low-battery":!1,lazyloading:!1,title:"Video background"};this.params=this.parseProperties(e,n,this.element,["data-ytbg-","data-vbg-"]),this.params.pause&&(this.params["play-button"]=this.params.pause),this.params.resolution_mod=L(this.params.resolution),this.muted=this.params.muted,this.volume=this.params.volume,this.currentTime=this.params["start-at"]||0,this.duration=this.params["end-at"]||0,this.percentComplete=0,this.params["start-at"]&&(this.percentComplete=this.timeToPercentage(this.params["start-at"])),this.buildWrapperHTML(),!(this.is_mobile&&!this.params.mobile)&&(this.params["play-button"]&&u(this,{name:"playing",className:"play-toggle",innerHtml:'',initialState:!this.paused,stateClassName:"paused",condition_parameter:"paused",stateChildClassNames:["fa-pause-circle","fa-play-circle"],actions:["play","pause"]}),this.params["mute-button"]&&u(this,{name:"muted",className:"mute-toggle",innerHtml:'',initialState:this.muted,stateClassName:"muted",condition_parameter:"muted",stateChildClassNames:["fa-volume-up","fa-volume-mute"],actions:["unmute","mute"]}))}timeToPercentage(t){if(t<=this.params["start-at"])return 0;if(t>=this.duration)return 100;if(t<=0)return 0;t-=this.params["start-at"];const e=this.duration-this.params["start-at"];return C(t,e)}percentageToTime(t){if(!this.duration)return this.params["start-at"]||0;if(t>100)return this.duration;if(t<=0)return this.params["start-at"]||0;const e=this.duration-this.params["start-at"];let i=t*e/100;return i=I(i,3),i>e&&(i=e),this.params["start-at"]&&(i+=this.params["start-at"]),i}resize(t){this.params["fit-box"]||O(t||this.playerElement,this.params.resolution_mod,this.params.offset),this.dispatchEvent("video-background-resize")}stylePlayerElement(t){t&&(this.params["inline-styles"]&&(t.style.top="50%",t.style.left="50%",t.style.transform="translateX(-50%) translateY(-50%)",t.style.position="absolute",t.style.opacity=0),this.params["fit-box"]&&(t.style.width="100%",t.style.height="100%"))}buildWrapperHTML(){const t=this.element.parentNode;this.element.classList.add("youtube-background","video-background");const e={height:"100%",width:"100%","z-index":"0",position:"absolute",overflow:"hidden",top:0,left:0,bottom:0,right:0};if(this.params["mute-button"]||(e["pointer-events"]="none"),(this.params["load-background"]||this.params.poster)&&(this.loadBackground(this.id),this.params.poster&&(e["background-image"]=`url(${this.params.poster})`),e["background-size"]="cover",e["background-repeat"]="no-repeat",e["background-position"]="center"),this.params["inline-styles"]){for(let i in e)this.element.style[i]=e[i];["absolute","fixed","relative","sticky"].indexOf(t.style.position)||(t.style.position="relative")}if(this.params["play-button"]||this.params["mute-button"]){const i=document.createElement("div");i.className="video-background-controls",i.style.position="absolute",i.style.top="10px",i.style.right="10px",i.style["z-index"]=2,this.controls_element=i,t.appendChild(i)}return this.element}loadBackground(t){this.params["load-background"]&&t&&(this.type==="youtube"&&(this.element.style["background-image"]=`url(https://img.youtube.com/vi/${t}/hqdefault.jpg)`),this.type==="vimeo"&&(this.element.style["background-image"]=`url(https://vumbnail.com/${t}.jpg)`))}destroy(){this.playerElement.remove(),this.element.classList.remove("youtube-background","video-background"),this.element.removeAttribute("data-vbg-uid"),this.element.style="",(this.params["play-button"]||this.params["mute-button"])&&this.controls_element.remove(),this.timeUpdateTimer&&clearInterval(this.timeUpdateTimer),this.dispatchEvent("video-background-destroyed")}setDuration(t){if(this.duration!==t){if(this.params["end-at"]){if(t>this.params["end-at"]){this.duration=this.params["end-at"];return}if(tt&&(this.duration=t),this.currentTime>t&&this.onVideoEnded()}dispatchEvent(t){this.element.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:this}))}shouldPlay(){return this.currentState==="ended"&&!this.params.loop?!1:!!(this.params["always-play"]&&this.currentState!=="playing"||this.isIntersecting&&this.params.autoplay&&this.currentState!=="playing")}mobileLowBatteryAutoplayHack(){if(!this.params["force-on-low-battery"]||!this.is_mobile&&this.params.mobile)return;const t=function(){!this.initialPlay&&this.params.autoplay&&this.params.muted&&(this.softPlay(),!this.isIntersecting&&!this.params["always-play"]&&this.softPause())};document.addEventListener("touchstart",t.bind(this),{once:!0})}parseProperties(t,e,i,s){let a={};if(!t)a=e;else for(let r in e)a[r]=t.hasOwnProperty(r)?t[r]:e[r];if(!i)return a;for(let r in a){let n;if(S(s))for(let o=0;o=this.duration){this.currentState="ended",this.dispatchEvent("video-background-state-change"),this.onVideoEnded(),this.stopTimeUpdateTimer();return}this.dispatchEvent("video-background-time-update")}}onVideoPlayerReady(){this.mobileLowBatteryAutoplayHack(),this.params.autoplay&&(this.params["always-play"]||this.isIntersecting)&&(this.params["start-at"]&&this.seekTo(this.params["start-at"]),this.player.playVideo()),this.setDuration(this.player.getDuration()),this.dispatchEvent("video-background-ready")}onVideoStateChange(t){this.currentState=this.convertState(t.data),this.currentState==="ended"&&this.onVideoEnded(),this.currentState==="notstarted"&&this.params.autoplay&&(this.seekTo(this.params["start-at"]),this.player.playVideo()),this.currentState==="playing"&&this.onVideoPlay(),this.currentState==="paused"&&this.onVideoPause(),this.dispatchEvent("video-background-state-change")}onVideoPlay(){this.initialPlay||(this.initialPlay=!0,this.playerElement.style.opacity=1);const t=this.player.getCurrentTime();this.params["start-at"]&&t=this.duration&&this.seekTo(this.params["start-at"]),this.duration||this.setDuration(this.player.getDuration()),this.dispatchEvent("video-background-play"),this.startTimeUpdateTimer()}onVideoPause(){this.stopTimeUpdateTimer(),this.dispatchEvent("video-background-pause")}onVideoEnded(){if(this.dispatchEvent("video-background-ended"),!this.params.loop)return this.pause();this.seekTo(this.params["start-at"]),this.player.playVideo()}seek(t){this.seekTo(this.percentageToTime(t),!0)}seekTo(t,e=!0){this.player&&(this.player.seekTo(t,e),this.dispatchEvent("video-background-seeked"))}softPause(){!this.player||this.currentState==="paused"||(this.stopTimeUpdateTimer(),this.player.pauseVideo())}softPlay(){!this.player||this.currentState==="playing"||this.player.playVideo()}play(){this.player&&(this.paused=!1,this.player.playVideo())}pause(){this.player&&(this.paused=!0,this.stopTimeUpdateTimer(),this.player.pauseVideo())}unmute(){this.player&&(this.muted=!1,this.initialVolume||(this.initialVolume=!0,this.setVolume(this.params.volume)),this.player.unMute(),this.dispatchEvent("video-background-unmute"))}mute(){this.player&&(this.muted=!0,this.player.mute(),this.dispatchEvent("video-background-mute"))}getVolume(){if(this.player)return this.player.getVolume()/100}setVolume(t){this.player&&(this.volume=t,this.player.setVolume(t*100),this.dispatchEvent("video-background-volume-change"))}},g=class extends h{constructor(t,e,i,s,a){super(t,e,i.id,s,"vimeo",a),i&&(this.unlisted=i.unlisted,!(this.is_mobile&&!this.params.mobile)&&(this.injectScript(),this.player=null,this.injectPlayer(),this.initVimeoPlayer()))}injectScript(){const t="https://player.vimeo.com/api/player.js";if(window.hasOwnProperty("Vimeo")||document.querySelector(`script[src="${t}"]`))return;const e=document.createElement("script");e.async=!0,window.hasOwnProperty("onVimeoIframeAPIReady")&&typeof window.onVimeoIframeAPIReady=="function"&&e.addEventListener("load",()=>{window.onVimeoIframeAPIReady()}),e.src=t;const i=document.getElementsByTagName("script")[0];i.parentNode.insertBefore(e,i)}initVimeoPlayer(){!window.hasOwnProperty("Vimeo")||this.player!==null||(this.player=new Vimeo.Player(this.playerElement),this.player.on("loaded",this.onVideoPlayerReady.bind(this)),this.player.on("ended",this.onVideoEnded.bind(this)),this.player.on("play",this.onVideoPlay.bind(this)),this.player.on("pause",this.onVideoPause.bind(this)),this.player.on("bufferstart",this.onVideoBuffering.bind(this)),this.player.on("timeupdate",this.onVideoTimeUpdate.bind(this)),this.volume!==1&&!this.muted&&this.setVolume(this.volume))}onVideoError(t){console.error(t)}generatePlayerElement(){const t=document.createElement("iframe");return this.params.title&&t.setAttribute("title",this.params.title),t.setAttribute("frameborder",0),t.setAttribute("allow","autoplay; mute"),this.params.lazyloading&&t.setAttribute("loading","lazy"),t}generateSrcURL(t,e){e=e?`h=${e}&`:"";let i=`https://player.vimeo.com/video/${t}?${e}background=1&controls=0`;return this.params.muted&&(i+="&muted=1"),this.params.autoplay&&(this.params["always-play"]||this.isIntersecting)&&(i+="&autoplay=1"),this.params.loop&&(i+="&loop=1&autopause=0"),this.params["no-cookie"]&&(i+="&dnt=1"),this.params["start-at"]&&(i+="#t="+this.params["start-at"]+"s"),i}injectPlayer(){this.playerElement=this.generatePlayerElement(),this.src=this.generateSrcURL(this.id,this.unlisted),this.playerElement.src=this.src,this.playerElement.id=this.uid,this.stylePlayerElement(this.playerElement),this.element.appendChild(this.playerElement),this.resize(this.playerElement)}updateState(t){this.currentState=t,this.dispatchEvent("video-background-state-change")}setSource(t){const e=t.match(m);!e||!e.length||(this.id=e[1],this.src=this.generateSrcURL(this.id),this.playerElement.src=this.src,this.element.hasAttribute("data-vbg")&&this.element.setAttribute("data-vbg",this.src),this.element.hasAttribute("data-ytbg")&&this.element.setAttribute("data-ytbg",this.src),this.loadBackground(this.id))}onVideoPlayerReady(){this.mobileLowBatteryAutoplayHack(),this.params["start-at"]&&this.seekTo(this.params["start-at"]),this.params.autoplay&&(this.params["always-play"]||this.isIntersecting)&&this.player.play(),this.player.getDuration().then(t=>{this.setDuration(t)}),this.dispatchEvent("video-background-ready")}onVideoEnded(){if(this.updateState("ended"),this.dispatchEvent("video-background-ended"),!this.params.loop)return this.pause();this.seekTo(this.params["start-at"]),this.updateState("playing"),this.dispatchEvent("video-background-play")}onVideoTimeUpdate(t){this.currentTime=t.seconds,this.percentComplete=this.timeToPercentage(t.seconds),this.dispatchEvent("video-background-time-update"),this.setDuration(t.duration),this.params["end-at"]&&this.duration&&t.seconds>=this.duration&&this.onVideoEnded()}onVideoBuffering(){this.updateState("buffering")}onVideoPlay(t){if(this.setDuration(t.duration),!this.initialPlay&&(this.initialPlay=!0,this.playerElement.style.opacity=1,this.player.setLoop(this.params.loop),!(this.params.autoplay&&(this.params["always-play"]||this.isIntersecting))))return this.player.pause();const e=t.seconds;this.params["start-at"]&&e=this.duration&&this.seekTo(this.params["start-at"]),this.updateState("playing"),this.dispatchEvent("video-background-play")}onVideoPause(){this.updateState("paused"),this.dispatchEvent("video-background-pause")}seek(t){this.seekTo(this.percentageToTime(t))}seekTo(t){this.player&&(this.player.setCurrentTime(t),this.dispatchEvent("video-background-seeked"))}softPause(){!this.player||this.currentState==="paused"||this.player.pause()}softPlay(){!this.player||this.currentState==="playing"||this.player.play()}play(){this.player&&(this.paused=!1,this.player.play())}pause(){this.player&&(this.paused=!0,this.player.pause())}unmute(){this.player&&(this.muted=!1,this.initialVolume||(this.initialVolume=!0,this.setVolume(this.params.volume)),this.player.setMuted(!1),this.dispatchEvent("video-background-unmute"))}mute(){this.player&&(this.muted=!0,this.player.setMuted(!0),this.dispatchEvent("video-background-mute"))}getVolume(){if(this.player)return this.player.getVolume()}setVolume(t){this.player&&(this.volume=t,this.player.setVolume(t),this.dispatchEvent("video-background-volume-change"))}},B=class extends h{constructor(t,e,i,s,a){super(t,e,i.link,s,"video",a),!(!i||!i.link)&&(this.is_mobile&&!this.params.mobile||(this.src=i.link,this.ext=/(?:\.([^.]+))?$/.exec(i.id)[1],this.uid=s,this.element.setAttribute("data-vbg-uid",s),this.player=null,this.buttons={},this.MIME_MAP={ogv:"video/ogg",ogm:"video/ogg",ogg:"video/ogg",avi:"video/avi",mp4:"video/mp4",webm:"video/webm",m4v:"video/x-m4v",mov:"video/quicktime",qt:"video/quicktime"},this.mime=this.MIME_MAP[this.ext.toLowerCase()],this.injectPlayer(),this.mobileLowBatteryAutoplayHack(),this.dispatchEvent("video-background-ready")))}generatePlayerElement(){const t=document.createElement("video");return this.params.title&&t.setAttribute("title",this.params.title),t.setAttribute("playsinline",""),this.params.loop&&t.setAttribute("loop",""),this.params.autoplay&&(this.params["always-play"]||this.isIntersecting)&&(t.setAttribute("autoplay",""),t.autoplay=!0),this.muted&&(t.setAttribute("muted",""),t.muted=!0),this.params.lazyloading&&t.setAttribute("loading","lazy"),t}injectPlayer(){this.player=this.generatePlayerElement(),this.playerElement=this.player,this.volume!==1&&!this.muted&&this.setVolume(this.volume),this.playerElement.setAttribute("id",this.uid),this.stylePlayerElement(this.playerElement),this.player.addEventListener("loadedmetadata",this.onVideoLoadedMetadata.bind(this)),this.player.addEventListener("durationchange",this.onVideoLoadedMetadata.bind(this)),this.player.addEventListener("canplay",this.onVideoCanPlay.bind(this)),this.player.addEventListener("timeupdate",this.onVideoTimeUpdate.bind(this)),this.player.addEventListener("play",this.onVideoPlay.bind(this)),this.player.addEventListener("pause",this.onVideoPause.bind(this)),this.player.addEventListener("waiting",this.onVideoBuffering.bind(this)),this.player.addEventListener("ended",this.onVideoEnded.bind(this)),this.element.appendChild(this.playerElement);const t=document.createElement("source");t.setAttribute("src",this.src),t.setAttribute("type",this.mime),this.playerElement.appendChild(t),this.resize(this.playerElement)}updateState(t){this.currentState=t,this.dispatchEvent("video-background-state-change")}setSource(t){const e=t.match(c);if(!e||!e.length)return;this.id=e[1],this.ext=/(?:\.([^.]+))?$/.exec(this.id)[1],this.mime=this.MIME_MAP[this.ext.toLowerCase()],this.playerElement.innerHTML="";const i=document.createElement("source");i.setAttribute("src",t),i.setAttribute("type",this.mime),this.playerElement.appendChild(i),this.src=t,this.element.hasAttribute("data-vbg")&&this.element.setAttribute("data-vbg",this.src),this.element.hasAttribute("data-ytbg")&&this.element.setAttribute("data-ytbg",this.src)}onVideoLoadedMetadata(){this.setDuration(this.player.duration)}onVideoCanPlay(){this.setDuration(this.player.duration)}onVideoTimeUpdate(){this.currentTime=this.player.currentTime,this.percentComplete=this.timeToPercentage(this.player.currentTime),this.dispatchEvent("video-background-time-update"),this.params["end-at"]&&this.currentTime>=this.duration&&this.onVideoEnded()}onVideoPlay(){this.initialPlay||(this.initialPlay=!0,this.playerElement.style.opacity=1);const t=this.player.currentTime;this.params["start-at"]&&t<=this.params["start-at"]&&this.seekTo(this.params["start-at"]),this.duration&&t>=this.duration&&this.seekTo(this.params["start-at"]),this.updateState("playing"),this.dispatchEvent("video-background-play")}onVideoPause(){this.updateState("paused"),this.dispatchEvent("video-background-pause")}onVideoEnded(){if(this.updateState("ended"),this.dispatchEvent("video-background-ended"),!this.params.loop)return this.pause();this.seekTo(this.params["start-at"]),this.onVideoPlay()}onVideoBuffering(){this.updateState("buffering")}seek(t){this.seekTo(this.percentageToTime(t))}seekTo(t){if(this.player){if(this.player.hasOwnProperty("fastSeek")){this.player.fastSeek(t);return}this.player.currentTime=t,this.dispatchEvent("video-background-seeked")}}softPause(){!this.player||this.currentState==="paused"||this.player.pause()}softPlay(){!this.player||this.currentState==="playing"||this.player.play()}play(){this.player&&(this.paused=!1,this.player.play())}pause(){this.player&&(this.paused=!0,this.player.pause())}unmute(){this.player&&(this.muted=!1,this.player.muted=!1,this.initialVolume||(this.initialVolume=!0,this.setVolume(this.params.volume)),this.dispatchEvent("video-background-unmute"))}mute(){this.player&&(this.muted=!0,this.player.muted=!0,this.dispatchEvent("video-background-mute"))}getVolume(){if(this.player)return this.player.volume}setVolume(t){this.player&&(this.volume=t,this.player.volume=t,this.dispatchEvent("video-background-volume-change"))}},b=class{constructor(t,e){this.elements=t,this.elements instanceof Element&&(this.elements=[this.elements]),typeof this.elements=="string"&&(this.elements=document.querySelectorAll(t)),this.index={};const i=this;if(this.intersectionObserver=null,"IntersectionObserver"in window&&(this.intersectionObserver=new IntersectionObserver(function(s){s.forEach(function(a){const r=a.target.getAttribute("data-vbg-uid");if(r&&i.index.hasOwnProperty(r)&&a.isIntersecting){i.index[r].isIntersecting=!0;try{i.index[r].player&&!i.index[r].paused&&i.index[r].softPlay()}catch{}}else{i.index[r].isIntersecting=!1;try{i.index[r].player&&i.index[r].softPause()}catch{}}})})),this.resizeObserver=null,"ResizeObserver"in window?this.resizeObserver=new ResizeObserver(function(s){s.forEach(function(a){const r=a.target.getAttribute("data-vbg-uid");r&&i.index.hasOwnProperty(r)&&window.requestAnimationFrame(()=>i.index[r].resize())})}):window.addEventListener("resize",function(){for(let s in i.index)window.requestAnimationFrame(()=>i.index[s].resize(i.index[s].playerElement))}),this.initPlayers(),!(!this.elements||!this.elements.length)){for(let s=0;s{function l(t){t&&(t.element.classList.add(t.stateClassName),t.element.firstChild.classList.remove(t.stateChildClassNames[0]),t.element.firstChild.classList.add(t.stateChildClassNames[1]),t.element.setAttribute("aria-checked",!1))}function E(t){t&&(t.element.classList.remove(t.stateClassName),t.element.firstChild.classList.add(t.stateChildClassNames[0]),t.element.firstChild.classList.remove(t.stateChildClassNames[1]),t.element.setAttribute("aria-checked",!0))}function u(t,e){const i=document.createElement("button");i.className=e.className,i.innerHTML=e.innerHtml,i.setAttribute("role","switch"),i.firstChild.classList.add(e.stateChildClassNames[0]),i.setAttribute("aria-checked",!e.initialState),e.element=i,t.params[e.condition_parameter]===e.initialState&&l(e),i.addEventListener("click",function(s){this.classList.contains(e.stateClassName)?(E(e),t[e.actions[0]]()):(l(e),t[e.actions[1]]())}),t.buttons[e.name]={element:i,button_properties:e},t.controls_element.appendChild(i)}function w(t){if(/^\s*(true|false)\s*$/i.test(t))return t==="true"}function k(t){if(/^\s*\d+\s*$/.test(t))return parseInt(t);if(/^\s*[\d.]+\s*$/.test(t))return parseFloat(t)}function T(t){if(/^\s*\[.*\]\s*$/.test(t))try{return JSON.parse(t)}catch{}}function V(t){if(/^\s*\{.*\}\s*$/.test(t))try{return JSON.parse(t)}catch{}}function P(t){if(/^\s*\/.*\/g?i?\s*$/.test(t))try{return new RegExp(t)}catch{}}function A(t){if(/^\s*null\s*$/.test(t))return null;const e=w(t);return e!==void 0?e:k(t)||T(t)||V(t)||P(t)||t}function S(t){return Array.isArray(t)}function x(t){return typeof t=="string"}function d(t,e,i=!1){if(t=Number(t),e=Number(e),isNaN(t)||isNaN(e))throw new TypeError("Both min and max must be numbers");if(t>e&&([t,e]=[e,t]),t===e)return t;t=Math.round(t),e=Math.round(e);const s=i?N():Math.random();return Math.floor(s*(e-t+1))+t}function I(t,e){return e?parseFloat(t.toFixed(e)):parseInt(t)}function C(t,e){return!t||!e||Number.isNaN(t)||Number.isNaN(e)?0:t/e*100}function N(){if(!crypto)return Math.random();if(crypto.getRandomValues)return crypto.getRandomValues(new Uint32Array(1))[0]/4294967295}var p=/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/i,m=/(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|video\/|)(\d+)(?:[a-zA-Z0-9_\-]+)?/i,c=/\/([^\/]+\.(?:mp4|ogg|ogv|ogm|webm|avi))\s*$/i;function L(t){const e=1.7777777778;if(!t||!t.length||/16[\:x\-\/]{1}9/i.test(t))return e;const i=t.split(/\s?[\:x\-\/]{1}\s?/i);if(i.length<2)return e;const s=parseInt(i[0]),a=parseInt(i[1]);return s===0||a===0||isNaN(s)||isNaN(a)?e:s/a}function y(t,e=document){if(t instanceof Array||t instanceof NodeList)return t;if(t instanceof Element)return[t];if(e instanceof Element||e instanceof Document)return e.querySelectorAll(t);if(x(e)&&(e=y(e)),!e instanceof Array&&!e instanceof NodeList)return[];const i=[];for(const s of e)i.push(...s.querySelectorAll(t));return i}function O(t,e=1,i=0){t instanceof Element&&(t=[t]),typeof t=="string"&&(t=y(t));for(const s of t){const a=s.parentNode.offsetHeight+i,r=s.parentNode.offsetWidth+i;e>r/a?(s.style.width=a*e+"px",s.style.height=a+"px"):(s.style.width=r+"px",s.style.height=r/e+"px")}}function U(t){return/\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(t)||/\b(Android|Windows Phone|iPad|iPod)\b/i.test(t)}function R(){return"maxTouchPoints"in navigator?navigator.maxTouchPoints>0:"matchMedia"in window?!!matchMedia("(pointer:coarse)").matches:"orientation"in window?!0:U(navigator.userAgent)}var h=class{constructor(t,e,i,s,a,r){if(!i)return;this.is_mobile=R(),this.type=a,this.id=i,this.factoryInstance=r,this.element=t,this.playerElement=null,this.uid=s,this.element.setAttribute("data-vbg-uid",s),this.buttons={},this.isIntersecting=!1,this.paused=!1,this.muted=!1,this.currentState="notstarted",this.initialPlay=!1,this.initialVolume=!1,this.volume=1,this.params={};const n={pause:!1,"play-button":!1,"mute-button":!1,autoplay:!0,muted:!0,loop:!0,mobile:!0,"load-background":!1,resolution:"16:9","inline-styles":!0,"fit-box":!1,offset:100,"start-at":0,"end-at":0,poster:null,"always-play":!1,volume:1,"no-cookie":!0,"force-on-low-battery":!1,lazyloading:!1,title:"Video background"};this.params=this.parseProperties(e,n,this.element,["data-ytbg-","data-vbg-"]),this.params.pause&&(this.params["play-button"]=this.params.pause),this.params.resolution_mod=L(this.params.resolution),this.muted=this.params.muted,this.volume=this.params.volume,this.currentTime=this.params["start-at"]||0,this.duration=this.params["end-at"]||0,this.percentComplete=0,this.params["start-at"]&&(this.percentComplete=this.timeToPercentage(this.params["start-at"])),this.buildWrapperHTML(),!(this.is_mobile&&!this.params.mobile)&&(this.params["play-button"]&&u(this,{name:"playing",className:"play-toggle",innerHtml:'',initialState:!this.paused,stateClassName:"paused",condition_parameter:"paused",stateChildClassNames:["fa-pause-circle","fa-play-circle"],actions:["play","pause"]}),this.params["mute-button"]&&u(this,{name:"muted",className:"mute-toggle",innerHtml:'',initialState:this.muted,stateClassName:"muted",condition_parameter:"muted",stateChildClassNames:["fa-volume-up","fa-volume-mute"],actions:["unmute","mute"]}))}timeToPercentage(t){if(t<=this.params["start-at"])return 0;if(t>=this.duration)return 100;if(t<=0)return 0;t-=this.params["start-at"];const e=this.duration-this.params["start-at"];return C(t,e)}percentageToTime(t){if(!this.duration)return this.params["start-at"]||0;if(t>100)return this.duration;if(t<=0)return this.params["start-at"]||0;const e=this.duration-this.params["start-at"];let i=t*e/100;return i=I(i,3),i>e&&(i=e),this.params["start-at"]&&(i+=this.params["start-at"]),i}resize(t){this.params["fit-box"]||O(t||this.playerElement,this.params.resolution_mod,this.params.offset),this.dispatchEvent("video-background-resize")}stylePlayerElement(t){t&&(this.params["inline-styles"]&&(t.style.top="50%",t.style.left="50%",t.style.transform="translateX(-50%) translateY(-50%)",t.style.position="absolute",t.style.opacity=0),this.params["fit-box"]&&(t.style.width="100%",t.style.height="100%"))}buildWrapperHTML(){const t=this.element.parentNode;this.element.classList.add("youtube-background","video-background");const e={height:"100%",width:"100%","z-index":"0",position:"absolute",overflow:"hidden",top:0,left:0,bottom:0,right:0};if(this.params["mute-button"]||(e["pointer-events"]="none"),(this.params["load-background"]||this.params.poster)&&(this.loadBackground(this.id),this.params.poster&&(e["background-image"]=`url(${this.params.poster})`),e["background-size"]="cover",e["background-repeat"]="no-repeat",e["background-position"]="center"),this.params["inline-styles"]){for(let i in e)this.element.style[i]=e[i];["absolute","fixed","relative","sticky"].indexOf(t.style.position)||(t.style.position="relative")}if(this.params["play-button"]||this.params["mute-button"]){const i=document.createElement("div");i.className="video-background-controls",i.style.position="absolute",i.style.top="10px",i.style.right="10px",i.style["z-index"]=2,this.controls_element=i,t.appendChild(i)}return this.element}loadBackground(t){this.params["load-background"]&&t&&(this.type==="youtube"&&(this.element.style["background-image"]=`url(https://img.youtube.com/vi/${t}/hqdefault.jpg)`),this.type==="vimeo"&&(this.element.style["background-image"]=`url(https://vumbnail.com/${t}.jpg)`))}destroy(){this.playerElement.remove(),this.element.classList.remove("youtube-background","video-background"),this.element.removeAttribute("data-vbg-uid"),this.element.style="",(this.params["play-button"]||this.params["mute-button"])&&this.controls_element.remove(),this.timeUpdateTimer&&clearInterval(this.timeUpdateTimer),this.dispatchEvent("video-background-destroyed")}setDuration(t){if(this.duration!==t){if(this.params["end-at"]){if(t>this.params["end-at"]){this.duration=this.params["end-at"];return}if(tt&&(this.duration=t),this.currentTime>t&&this.onVideoEnded()}dispatchEvent(t){this.element.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:this}))}shouldPlay(){return this.currentState==="ended"&&!this.params.loop?!1:!!(this.params["always-play"]&&this.currentState!=="playing"||this.isIntersecting&&this.params.autoplay&&this.currentState!=="playing")}mobileLowBatteryAutoplayHack(){if(!this.params["force-on-low-battery"]||!this.is_mobile&&this.params.mobile)return;const t=function(){!this.initialPlay&&this.params.autoplay&&this.params.muted&&(this.softPlay(),!this.isIntersecting&&!this.params["always-play"]&&this.softPause())};document.addEventListener("touchstart",t.bind(this),{once:!0})}parseProperties(t,e,i,s){let a={};if(!t)a=e;else for(let r in e)a[r]=t.hasOwnProperty(r)?t[r]:e[r];if(!i)return a;for(let r in a){let n;if(S(s))for(let o=0;o=this.duration){this.currentState="ended",this.dispatchEvent("video-background-state-change"),this.onVideoEnded(),this.stopTimeUpdateTimer();return}this.dispatchEvent("video-background-time-update")}}onVideoPlayerReady(){this.mobileLowBatteryAutoplayHack(),this.params.autoplay&&(this.params["always-play"]||this.isIntersecting)&&(this.params["start-at"]&&this.seekTo(this.params["start-at"]),this.player.playVideo()),this.setDuration(this.player.getDuration()),this.dispatchEvent("video-background-ready")}onVideoStateChange(t){this.currentState=this.convertState(t.data),this.currentState==="ended"&&this.onVideoEnded(),this.currentState==="notstarted"&&this.params.autoplay&&(this.seekTo(this.params["start-at"]),this.player.playVideo()),this.currentState==="playing"&&this.onVideoPlay(),this.currentState==="paused"&&this.onVideoPause(),this.dispatchEvent("video-background-state-change")}onVideoPlay(){this.initialPlay||(this.initialPlay=!0,this.playerElement.style.opacity=1);const t=this.player.getCurrentTime();this.params["start-at"]&&t=this.duration&&this.seekTo(this.params["start-at"]),this.duration||this.setDuration(this.player.getDuration()),this.dispatchEvent("video-background-play"),this.startTimeUpdateTimer()}onVideoPause(){this.stopTimeUpdateTimer(),this.dispatchEvent("video-background-pause")}onVideoEnded(){if(this.dispatchEvent("video-background-ended"),!this.params.loop)return this.pause();this.seekTo(this.params["start-at"]),this.player.playVideo()}seek(t){this.seekTo(this.percentageToTime(t),!0)}seekTo(t,e=!0){this.player&&(this.player.seekTo(t,e),this.dispatchEvent("video-background-seeked"))}softPause(){!this.player||this.currentState==="paused"||(this.stopTimeUpdateTimer(),this.player.pauseVideo())}softPlay(){!this.player||this.currentState==="playing"||this.player.playVideo()}play(){this.player&&(this.paused=!1,this.player.playVideo())}pause(){this.player&&(this.paused=!0,this.stopTimeUpdateTimer(),this.player.pauseVideo())}unmute(){this.player&&(this.muted=!1,this.initialVolume||(this.initialVolume=!0,this.setVolume(this.params.volume)),this.player.unMute(),this.dispatchEvent("video-background-unmute"))}mute(){this.player&&(this.muted=!0,this.player.mute(),this.dispatchEvent("video-background-mute"))}getVolume(){if(this.player)return this.player.getVolume()/100}setVolume(t){this.player&&(this.volume=t,this.player.setVolume(t*100),this.dispatchEvent("video-background-volume-change"))}},g=class extends h{constructor(t,e,i,s,a){super(t,e,i.id,s,"vimeo",a),i&&(this.unlisted=i.unlisted,!(this.is_mobile&&!this.params.mobile)&&(this.injectScript(),this.player=null,this.injectPlayer(),this.initVimeoPlayer()))}injectScript(){const t="https://player.vimeo.com/api/player.js";if(window.hasOwnProperty("Vimeo")||document.querySelector(`script[src="${t}"]`))return;const e=document.createElement("script");e.async=!0,window.hasOwnProperty("onVimeoIframeAPIReady")&&typeof window.onVimeoIframeAPIReady=="function"&&e.addEventListener("load",()=>{window.onVimeoIframeAPIReady()}),e.src=t;const i=document.getElementsByTagName("script")[0];i.parentNode.insertBefore(e,i)}initVimeoPlayer(){!window.hasOwnProperty("Vimeo")||this.player!==null||(this.player=new Vimeo.Player(this.playerElement),this.player.on("loaded",this.onVideoPlayerReady.bind(this)),this.player.on("ended",this.onVideoEnded.bind(this)),this.player.on("play",this.onVideoPlay.bind(this)),this.player.on("pause",this.onVideoPause.bind(this)),this.player.on("bufferstart",this.onVideoBuffering.bind(this)),this.player.on("timeupdate",this.onVideoTimeUpdate.bind(this)),this.volume!==1&&!this.muted&&this.setVolume(this.volume))}onVideoError(t){console.error(t)}generatePlayerElement(){const t=document.createElement("iframe");return this.params.title&&t.setAttribute("title",this.params.title),t.setAttribute("frameborder",0),t.setAttribute("allow","autoplay; mute"),this.params.lazyloading&&t.setAttribute("loading","lazy"),t}generateSrcURL(t,e){e=e?`h=${e}&`:"";let i=`https://player.vimeo.com/video/${t}?${e}background=1&controls=0`;return this.params.muted&&(i+="&muted=1"),this.params.autoplay&&(this.params["always-play"]||this.isIntersecting)&&(i+="&autoplay=1"),this.params.loop&&(i+="&loop=1&autopause=0"),this.params["no-cookie"]&&(i+="&dnt=1"),this.params["start-at"]&&(i+="#t="+this.params["start-at"]+"s"),i}injectPlayer(){this.playerElement=this.generatePlayerElement(),this.src=this.generateSrcURL(this.id,this.unlisted),this.playerElement.src=this.src,this.playerElement.id=this.uid,this.stylePlayerElement(this.playerElement),this.element.appendChild(this.playerElement),this.resize(this.playerElement)}updateState(t){this.currentState=t,this.dispatchEvent("video-background-state-change")}setSource(t){const e=t.match(m);!e||!e.length||(this.id=e[1],this.src=this.generateSrcURL(this.id),this.playerElement.src=this.src,this.element.hasAttribute("data-vbg")&&this.element.setAttribute("data-vbg",this.src),this.element.hasAttribute("data-ytbg")&&this.element.setAttribute("data-ytbg",this.src),this.loadBackground(this.id))}onVideoPlayerReady(){this.mobileLowBatteryAutoplayHack(),this.params["start-at"]&&this.seekTo(this.params["start-at"]),this.params.autoplay&&(this.params["always-play"]||this.isIntersecting)&&this.player.play(),this.player.getDuration().then(t=>{this.setDuration(t)}),this.dispatchEvent("video-background-ready")}onVideoEnded(){if(this.updateState("ended"),this.dispatchEvent("video-background-ended"),!this.params.loop)return this.pause();this.seekTo(this.params["start-at"]),this.updateState("playing"),this.dispatchEvent("video-background-play")}onVideoTimeUpdate(t){this.currentTime=t.seconds,this.percentComplete=this.timeToPercentage(t.seconds),this.dispatchEvent("video-background-time-update"),this.setDuration(t.duration),this.params["end-at"]&&this.duration&&t.seconds>=this.duration&&this.onVideoEnded()}onVideoBuffering(){this.updateState("buffering")}onVideoPlay(t){if(this.setDuration(t.duration),!this.initialPlay&&(this.initialPlay=!0,this.playerElement.style.opacity=1,this.player.setLoop(this.params.loop),!(this.params.autoplay&&(this.params["always-play"]||this.isIntersecting))))return this.player.pause();const e=t.seconds;this.params["start-at"]&&e=this.duration&&this.seekTo(this.params["start-at"]),this.updateState("playing"),this.dispatchEvent("video-background-play")}onVideoPause(){this.updateState("paused"),this.dispatchEvent("video-background-pause")}seek(t){this.seekTo(this.percentageToTime(t))}seekTo(t){this.player&&(this.player.setCurrentTime(t),this.dispatchEvent("video-background-seeked"))}softPause(){!this.player||this.currentState==="paused"||this.player.pause()}softPlay(){!this.player||this.currentState==="playing"||this.player.play()}play(){this.player&&(this.paused=!1,this.player.play())}pause(){this.player&&(this.paused=!0,this.player.pause())}unmute(){this.player&&(this.muted=!1,this.initialVolume||(this.initialVolume=!0,this.setVolume(this.params.volume)),this.player.setMuted(!1),this.dispatchEvent("video-background-unmute"))}mute(){this.player&&(this.muted=!0,this.player.setMuted(!0),this.dispatchEvent("video-background-mute"))}getVolume(){if(this.player)return this.player.getVolume()}setVolume(t){this.player&&(this.volume=t,this.player.setVolume(t),this.dispatchEvent("video-background-volume-change"))}},B=class extends h{constructor(t,e,i,s,a){super(t,e,i.link,s,"video",a),!(!i||!i.link)&&(this.is_mobile&&!this.params.mobile||(this.src=i.link,this.ext=/(?:\.([^.]+))?$/.exec(i.id)[1],this.uid=s,this.element.setAttribute("data-vbg-uid",s),this.player=null,this.buttons={},this.MIME_MAP={ogv:"video/ogg",ogm:"video/ogg",ogg:"video/ogg",avi:"video/avi",mp4:"video/mp4",webm:"video/webm",m4v:"video/x-m4v",mov:"video/quicktime",qt:"video/quicktime"},this.mime=this.MIME_MAP[this.ext.toLowerCase()],this.injectPlayer(),this.mobileLowBatteryAutoplayHack(),this.dispatchEvent("video-background-ready")))}generatePlayerElement(){const t=document.createElement("video");return this.params.title&&t.setAttribute("title",this.params.title),t.setAttribute("playsinline",""),this.params.loop&&t.setAttribute("loop",""),this.params.autoplay&&(this.params["always-play"]||this.isIntersecting)&&(t.setAttribute("autoplay",""),t.autoplay=!0),this.muted&&(t.setAttribute("muted",""),t.muted=!0),this.params.lazyloading&&t.setAttribute("loading","lazy"),t}injectPlayer(){this.player=this.generatePlayerElement(),this.playerElement=this.player,this.volume!==1&&!this.muted&&this.setVolume(this.volume),this.playerElement.setAttribute("id",this.uid),this.stylePlayerElement(this.playerElement),this.player.addEventListener("loadedmetadata",this.onVideoLoadedMetadata.bind(this)),this.player.addEventListener("durationchange",this.onVideoLoadedMetadata.bind(this)),this.player.addEventListener("canplay",this.onVideoCanPlay.bind(this)),this.player.addEventListener("timeupdate",this.onVideoTimeUpdate.bind(this)),this.player.addEventListener("play",this.onVideoPlay.bind(this)),this.player.addEventListener("pause",this.onVideoPause.bind(this)),this.player.addEventListener("waiting",this.onVideoBuffering.bind(this)),this.player.addEventListener("ended",this.onVideoEnded.bind(this)),this.element.appendChild(this.playerElement);const t=document.createElement("source");t.setAttribute("src",this.src),t.setAttribute("type",this.mime),this.playerElement.appendChild(t),this.resize(this.playerElement)}updateState(t){this.currentState=t,this.dispatchEvent("video-background-state-change")}setSource(t){const e=t.match(c);if(!e||!e.length)return;this.id=e[1],this.ext=/(?:\.([^.]+))?$/.exec(this.id)[1],this.mime=this.MIME_MAP[this.ext.toLowerCase()],this.playerElement.innerHTML="";const i=document.createElement("source");i.setAttribute("src",t),i.setAttribute("type",this.mime),this.playerElement.appendChild(i),this.src=t,this.element.hasAttribute("data-vbg")&&this.element.setAttribute("data-vbg",this.src),this.element.hasAttribute("data-ytbg")&&this.element.setAttribute("data-ytbg",this.src)}onVideoLoadedMetadata(){this.setDuration(this.player.duration)}onVideoCanPlay(){this.setDuration(this.player.duration)}onVideoTimeUpdate(){this.currentTime=this.player.currentTime,this.percentComplete=this.timeToPercentage(this.player.currentTime),this.dispatchEvent("video-background-time-update"),this.params["end-at"]&&this.currentTime>=this.duration&&this.onVideoEnded()}onVideoPlay(){this.initialPlay||(this.initialPlay=!0,this.playerElement.style.opacity=1);const t=this.player.currentTime;this.params["start-at"]&&t<=this.params["start-at"]&&this.seekTo(this.params["start-at"]),this.duration&&t>=this.duration&&this.seekTo(this.params["start-at"]),this.updateState("playing"),this.dispatchEvent("video-background-play")}onVideoPause(){this.updateState("paused"),this.dispatchEvent("video-background-pause")}onVideoEnded(){if(this.updateState("ended"),this.dispatchEvent("video-background-ended"),!this.params.loop)return this.pause();this.seekTo(this.params["start-at"]),this.onVideoPlay()}onVideoBuffering(){this.updateState("buffering")}seek(t){this.seekTo(this.percentageToTime(t))}seekTo(t){if(this.player){if(this.player.hasOwnProperty("fastSeek")){this.player.fastSeek(t);return}this.player.currentTime=t,this.dispatchEvent("video-background-seeked")}}softPause(){!this.player||this.currentState==="paused"||this.player.pause()}softPlay(){!this.player||this.currentState==="playing"||this.player.play()}play(){this.player&&(this.paused=!1,this.player.play())}pause(){this.player&&(this.paused=!0,this.player.pause())}unmute(){this.player&&(this.muted=!1,this.player.muted=!1,this.initialVolume||(this.initialVolume=!0,this.setVolume(this.params.volume)),this.dispatchEvent("video-background-unmute"))}mute(){this.player&&(this.muted=!0,this.player.muted=!0,this.dispatchEvent("video-background-mute"))}getVolume(){if(this.player)return this.player.volume}setVolume(t){this.player&&(this.volume=t,this.player.volume=t,this.dispatchEvent("video-background-volume-change"))}},b=class{constructor(t,e){this.elements=t,this.elements instanceof Element&&(this.elements=[this.elements]),typeof this.elements=="string"&&(this.elements=document.querySelectorAll(t)),this.index={};const i=this;if(this.intersectionObserver=null,"IntersectionObserver"in window&&(this.intersectionObserver=new IntersectionObserver(function(s){s.forEach(function(a){const r=a.target.getAttribute("data-vbg-uid");if(r&&i.index.hasOwnProperty(r)&&a.isIntersecting){i.index[r].isIntersecting=!0;try{i.index[r].player&&!i.index[r].paused&&i.index[r].softPlay()}catch{}}else{i.index[r].isIntersecting=!1;try{i.index[r].player&&i.index[r].softPause()}catch{}}})})),this.resizeObserver=null,"ResizeObserver"in window?this.resizeObserver=new ResizeObserver(function(s){s.forEach(function(a){const r=a.target.getAttribute("data-vbg-uid");r&&i.index.hasOwnProperty(r)&&window.requestAnimationFrame(()=>i.index[r].resize())})}):window.addEventListener("resize",function(){for(let s in i.index)window.requestAnimationFrame(()=>i.index[s].resize(i.index[s].playerElement))}),this.initPlayers(),!(!this.elements||!this.elements.length)){for(let s=0;s + {% if paragraph.sa_hero_background.value is not empty %} + {% if content.sa_hero_background[0]['#media'].bundle.value.0.target_id == 'image' %} +
+ {{ content.sa_hero_background }} +
+ {% elseif content.sa_hero_background[0]['#media'].bundle.value.0.target_id == 'remote_video' %} +
+ {% else %} +
+ {% endif %} + {% endif %} +
+ {% if paragraph.sa_width.value is not empty %} +
+
+ {% endif %} + {% block content %} +
+
+ {{ content.sa_hero_content }} +
+
+ {% endblock %} + {% if paragraph.sa_width.value is not empty %} +
+
+ {% endif %} +
+ +{% endblock paragraph %}