if(typeof hasPluginRequirements === 'undefined') {
    function hasPluginRequirements(pluginName, requirements) {
        let noRequiredFunctions = []

        requirements.forEach(function (requirement) {
            if (typeof this[requirement] !== 'function') {
                noRequiredFunctions.push(requirement)
            }
        })

        if (noRequiredFunctions.length) {
            console.error(`BLACKBOOK plugin "${pluginName}": requires ${noRequiredFunctions.join('(), ')}() functions`)
        }
        return !noRequiredFunctions.length
    }
}

if(typeof selectAll === 'undefined') {
    function selectAll(selector, container = false) {
        return Array.from(!container ? document.querySelectorAll(selector) : container.querySelectorAll(selector));
    }
}
if(typeof printf === 'undefined') {
    function printf(string, vars = [], addToEnd = true, char = '&') {
        vars.forEach(function (thisVar, index) {
            let r = new RegExp(char + (index + 1) + '(?![0-9])', 'g');
    
            if(r.test(string)) {
                string = string.replace(r, thisVar);
            } else if(addToEnd) {
                string += ' '+thisVar
            }
        })
        return string;
    }
}

if(typeof setCookie === 'undefined') {
    function setCookie(name, value, days, path = '/') {
        var expires = "";
        if (days) {
            var date = new Date();
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            expires = "; expires=" + date.toUTCString();
        }
        document.cookie = name + "=" + (value || "") + expires + (path ? `; path=${path}` : ';');
    }
}
if(typeof getCookie === 'undefined') {
    function getCookie(name) {
        var nameEQ = name + "=";
        var ca = document.cookie.split(';');
        for (var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == ' ') c = c.substring(1, c.length);
            if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
        }
        return null;
    }
}
if(typeof eraseCookie === 'undefined') {
    function eraseCookie(name) {
        document.cookie = name + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
    }
}
if(typeof indexOf === 'undefined') {
    function indexOf(el) {
        return Array.from(el.parentElement.children).indexOf(el)
    }
}
if(typeof closestEl === 'undefined') {
    function closestEl(el, closestParent){
        let foundClosest = false;
        let nextParent = el;
        let i = 0

        while (true){
            i++;
            if(!nextParent || nextParent.nodeName === 'BODY' || i > 20){
                break;
            }
            if(nextParent == closestParent){
                foundClosest = nextParent
                break;
            }
            nextParent = nextParent.parentElement
        }

        return foundClosest;
    }
}

if(typeof dynamicListener === 'undefined') {
  function dynamicListener(events, selector, handler, context){
    events.split(' ').forEach(function (event) {
      (document || context).addEventListener(event, function (e) {
        if(e.target.matches(selector) || e.target.closest(selector)){
          handler.call(e.target.closest(selector), e);
        }
      })
    })
  }
}
if(typeof trigger === 'undefined') {
    function trigger(el, eventName, params = {}) {
        //trigger('click',  undefined,   undefined)
        //trigger('click',  {},          undefined)
        //trigger(el,       'click',     undefined)
        //trigger(el,       'click',     {})
        // passed data will be at e.detail (JS), and e.originalEvent.detail (jQuery)
        let thisEl = el
        let thisEventName = eventName
        let thisParams = params

        if(typeof el === 'string'){
            thisEventName = el
            thisEl = document
            if(typeof eventName === 'object'){
                thisParams = eventName
            }
        }

        let newEvent = new CustomEvent(thisEventName, { bubbles: true, detail: thisParams });
        thisEl.dispatchEvent(newEvent)
    }
}
if(typeof animateOpacity === 'undefined') {
    function animateOpacity(element, duration, display = 'block', targetOpacity, onComplete) {
        const elStyles = window.getComputedStyle(element)
        const startOpacity = elStyles.display === 'none' ? 0 : parseFloat(elStyles.opacity)
        let currentOpacity = startOpacity;

        if(startOpacity !== 1) {
            duration = duration - (duration * startOpacity);
        }

        if (targetOpacity) {
            element.style.opacity = startOpacity
            element.style.display = display
        }

        if (element.animation && element.animation.stop) {
            element.animation.stop()
        }

        function updateOpacity() {
            const elapsedTime = performance.now() - startTime;
            const progress = Math.min(1, elapsedTime / duration);
            currentOpacity = startOpacity + progress * (targetOpacity - startOpacity);
            element.style.opacity = currentOpacity.toFixed(2);

            if (progress < 1) {
                element.animation.raf = requestAnimationFrame(updateOpacity);
            } else {
                element.style.opacity = ''
                element.animation = false
                if (!targetOpacity) {
                    element.style.display = 'none'
                }
                if (onComplete) {
                    onComplete();
                }
            }
        }

        function stopAnimation() {
            cancelAnimationFrame(element.animation.raf);
        }

        const startTime = performance.now();
        element.animation = {
            raf: 0,
            type: targetOpacity ? 'fadeIn' : 'fadeOut',
            stop: stopAnimation,
        };
        updateOpacity();
    }
}
if(typeof fadeIn === 'undefined') {
    function fadeIn(el, timeout, display = 'block', afterFunc = false) {
        animateOpacity(el, timeout, display, 1, afterFunc);
    }
}
if(typeof fadeOut === 'undefined') {
    function fadeOut(el, timeout, afterFunc = false) {
        animateOpacity(el, timeout, '', 0, afterFunc);
    }
}
if(typeof fadeToggle === 'undefined') {
    function fadeToggle(target, duration = 300, display = 'block', afterFunction = false) {
        if ((target.animation && target.animation.type === 'fadeOut') || (!target.animation && window.getComputedStyle(target).display === 'none')) {
            return fadeIn(target, duration, display, afterFunction);
        } else {
            return fadeOut(target, duration, afterFunction);
        }
    }
}
if(typeof animateHeight === 'undefined') {
    function animateHeight(element, duration, startHeight, targetHeight, onComplete) {
        let elStyles = window.getComputedStyle(element)
        let paddingTop = elStyles.paddingTop.replace('px', '')
        let paddingBottom = elStyles.paddingBottom.replace('px', '')
        if (!startHeight) {
            startHeight = element.clientHeight
        }
        let currentHeight = startHeight;


        element.style.transition = '';

        if (targetHeight) {
            if (paddingTop) {
                startHeight -= paddingTop
                element.style.paddingTop = '0'
                setTimeout(function () {
                    element.style.transition = `padding ${duration}ms linear`
                    element.style.paddingTop = paddingTop + 'px'
                }, 10)
            }
            if (paddingBottom) {
                startHeight -= paddingBottom
                element.style.paddingBottom = '0px'
                setTimeout(function () {
                    element.style.transition = `padding ${duration}ms linear`
                    element.style.paddingBottom = paddingBottom + 'px'
                }, 10)
            }
            currentHeight = startHeight
        } else {
            if (paddingTop) {
                element.style.transition = `padding ${duration}ms linear`
                element.style.paddingTop = '0px'
            }
            if (paddingBottom) {
                element.style.transition = `padding ${duration}ms linear`
                element.style.paddingBottom = '0px'
            }
        }
        if ((startHeight / targetHeight) !== Infinity) {
            duration = duration - (duration * (startHeight / targetHeight))
        }

        if (element.animation && element.animation.stop) {
            element.animation.stop();
        }

        function updateHeight() {
            const elapsedTime = performance.now() - startTime;
            const progress = Math.min(1, elapsedTime / duration);
            currentHeight = startHeight + progress * (targetHeight - startHeight);
            element.style.height = currentHeight.toFixed(2) + 'px';

            if (progress < 1) {
                element.animation.raf = requestAnimationFrame(updateHeight);
            } else {
                element.style.height = '';
                element.style.paddingTop = '';
                element.style.paddingBottom = '';
                element.style.overflow = '';
                element.style.transition = '';
                element.animation = false;
                if (!targetHeight) {
                    element.style.display = 'none'
                }
                if (onComplete) {
                    onComplete();
                }
            }
        }

        function stopAnimation() {
            cancelAnimationFrame(element.animation.raf);
        }

        const startTime = performance.now();
        element.animation = {
            raf: 0,
            type: targetHeight > startHeight ? 'slideDown' : 'slideUp',
            stop: stopAnimation,
        };
        updateHeight();
    }
}
if(typeof slideDown === 'undefined') {
    function slideDown(element, duration, display = 'block', onComplete) {
        let startHeight = element.clientHeight;
        element.style.display = display;
        element.style.height = 'unset'
        const targetHeight = element.clientHeight;
        element.style.height = '0px';
        element.style.overflow = 'hidden';

        animateHeight(element, duration, startHeight, targetHeight, onComplete);
    }
}
if(typeof slideUp === 'undefined') {
    function slideUp(element, duration, onComplete) {
        const targetHeight = 0;
        element.style.overflow = 'hidden';

        animateHeight(element, duration, false, targetHeight, onComplete);
    }
}
if(typeof slideToggle === 'undefined') {
    function slideToggle(target, duration, display = 'block', afterFunction) {
        if ((target.animation && target.animation.type === 'slideUp') || (!target.animation && window.getComputedStyle(target).display === 'none')) {
            return slideDown(target, duration, display, afterFunction);
        } else {
            return slideUp(target, duration, afterFunction);
        }
    }
}
if(typeof isElementInViewport === 'undefined') {
  function isElementInViewport(el) {
    if (typeof jQuery === "function" && el instanceof jQuery) {
      el = el[0];
    }
    if (!el) return false;
    let x = el.getBoundingClientRect().left;
    let y = el.getBoundingClientRect().top;
    let ww = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
    let hw = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
    let w = el.clientWidth;
    let h = el.clientHeight;
    return (
      (y < hw &&
        y + h > 0) &&
      (x < ww &&
        x + w > 0)
    );
  }
}

