/**
 * File default.js.
 * Theme Frontend scripts.
 */

(function() {
    
    if (typeof $ === 'undefined') $ = jQuery;
    // $(document).ready(function() {
    $(function() {
        // init js
        theme.init();

        console.log("Frontend Scripts: loaded");
    } );

} )(jQuery);

var theme = new function() {
    this.debug = true;
    this.window = { width:window.innerWidth, height:window.innerHeight };
    
    this.init = function() {
        icons.init();
        custom_inputs.init();
        content_wrapper.init();
        posts.init(); // refactor to query
        announce.init();
        theme.count();
        sorting.init(); // refactor to query
        tablesort.init(); // refactor to query
        counter.init();
        vc_anim.init(); // deprecated
        greyd_trigger.init(); // deprecated
    }
    this.count = function() {
        var count = sessionStorage.getItem('count');
        if (!count) count = 0; 
        count++;
        sessionStorage.setItem('count', count);
    }

    // local storage handler
	this.setLocal = function(name, value) {
		var tmp = localStorage.getItem(name);
        if (!tmp)
            localStorage.setItem(name, JSON.stringify(value));
	}
	this.getLocal = function(name) {
        return tmp = JSON.parse(localStorage.getItem(name));
    }
    this.delLocal = function(name) {
        localStorage.removeItem(name);
    }
}

function onYouTubeIframeAPIReady() {
    icons.initYoutubeVideos();
}
/**
 * deprecated but still used by VC content.
 * moved to Plugin and split into 'greyd.backgrounds', 'greyd.lazyload' and 'greyd.lottie'.
 * classes have changed in Blocks Rendering so the new functions are used instead.
 */
var icons = new function() {
	this.anims = {};
	this.bgs = {};

    this.init = function() {
        // get json icons
        if ( typeof lottie_setup !== 'undefined' && lottie_setup.mode === 'lazy' ) {
            var is_mobile = ($('html').hasClass('ios') || $('html').hasClass('android') || $('html').hasClass('vc_mobile'));
            if (!is_mobile || lottie_setup.mobile === "true") {
                if ($('.bm_icon').length > 0) {
                    setTimeout(function() {
                        $.getScript(lottie_setup.src, function() {
                            // console.log("lottie lib loaded");
                            //setTimeout(function() {
                                icons.initIcons();
                                icons.scroll();
                            //}, lottie_setup.time*1000);
                        });
                    }, lottie_setup.time*1000);
                }
                // else console.log("no animations found");
            }
        } 
        else {
            // console.log("direct loading of anims.");
            icons.initIcons();
        }

        // get background
        var youtubefound = false;
        $('.vc_bg').each(function(index) {
            // console.log($(this).attr('id'));
            var id = $(this).attr('id');
            var that = $(this);
            while (typeof id === 'undefined') {
                that = that.parent();
                // console.log(that);
                if (that.length == 0) id = "";
                else id = that.attr('id');
            }
            if (id != "") {
                var resize = false;
                if ($(this).hasClass('vc_bg_full') || $(this).hasClass('vc_bg_window')) 
                    resize = true;
                var scroll = [];
                $('#'+id+' .vc_bg_vparallax').each(function() {
                    scroll.push({ 
                        container: this,
                        scroll: 'vparallax'
                    });
                });
                $('#'+id+' .vc_bg_hparallax').each(function() {
                    scroll.push({ 
                        container: this,
                        scroll: 'hparallax'
                    });
                });
                var video = false;
                $('#'+id+' .vc_bg_video, #'+id+' .bg_video').each(function() {
                    video = {
                        src: $(this).children(0).data('vc_bg_video_src'),
                        video_id: $(this).children(0).data('vc_bg_video_id'),
                        video_aspect: $(this).children(0).data('vc_bg_video_ar'),
                        container_id: $(this).children(0).children(0).attr('id'),
                    };
                    if (video.src == "youtube") youtubefound = true;
                });
                if (!icons.bgs[id]) {
                    icons.bgs[id] = { 
                        container: this,
                        resize: resize,
                        scroll: scroll,
                        video: video
                    };
                }
            }
        });
        if (youtubefound) {
            var tag = document.createElement('script');
            tag.src = "https://www.youtube.com/iframe_api";
            var firstScriptTag = document.getElementsByTagName('script')[0];
            firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
        }

        this.addEvents();
        // console.log('Animation Script: loaded');
    }
    this.initYoutubeVideos = function() {
        for (var i in icons.bgs) {
            // all backgrounds
            if (icons.bgs[i].video && icons.bgs[i].video.src == "youtube") {
                // all youtube videos
                icons.bgs[i].video.player = new YT.Player(icons.bgs[i].video.container_id, {
                    height: '100%',
                    width: '100%',
                    videoId: icons.bgs[i].video.video_id,
                    playerVars: {
                        autoplay: '1',
                        loop: '1',
                        controls: '0',
                        modestbranding: '1',
                        disablekb: '1'
                    },
                    events: {
                        'onReady': icons.onPlayerReady,
                        'onStateChange': icons.onPlayerStateChange
                    }
                });
            }
        }
    }
    this.onPlayerReady = function(event) {
        // console.log("player ready");
        event.target.mute();
        event.target.playVideo();
        icons.resize();
        //icons.scroll(); // already gets called via icons.resize()
    }
    this.onPlayerStateChange = function(event) {
        if (event.data == YT.PlayerState.ENDED) {
            event.target.seekTo(0);
            event.target.playVideo();
        }
    }
    this.initIcons = function () {
        if (typeof bodymovin === 'undefined') return;

        $('body').trigger('lottieLoaded');
        
        $('.bm_icon').each(function(index) {
            // console.log($(this).attr('id'));
            var id = $(this).attr('id');
            var src = $(this).data('src');
            var anim = $(this).data('anim');
            var auto = false;
            if (anim == "auto") auto = true;
            icons.addIcon(id, src, auto);
            $(this).removeAttr('data-src');
        });
    }
	this.addIcon = function(icon, icon_path, autoplay) {
        if (typeof bodymovin === 'undefined') return false;
        if (autoplay == undefined) autoplay = false;
		//console.log(icon+', '+icon_path+', '+autoplay);
		if (!icons.anims[icon]) {
            if ( typeof lottie_setup !== 'undefined' && lottie_setup.lazyload === 'true' ) {
                // lazy load
                icons.anims[icon] = { 'status': "lazy", 'icon': icon, 'icon_path': icon_path, 'autoplay': autoplay };
            }
            else {
                // now
                icons.loadIcon( { 'icon': icon, 'icon_path': icon_path, 'autoplay': autoplay } );
            }
        }
	}
    this.loadIcon = function(config) { 
        // console.log(config);
        var icon = config.icon;
        var autoplay = config.autoplay;
        var icon_path = config.icon_path;
        icons.anims[icon] = bodymovin.loadAnimation( {
                                container: document.getElementById(icon),
                                renderer: 'svg',
                                loop: true,
                                autoplay: autoplay,
                                path: icon_path
                            } );
        icons.anims[icon].setSubframe(false);
        icons.anims[icon].addEventListener('DOMLoaded', function(e) { 
            // console.log('element loaded'); 
            $('#'+icon).parent().find('.bm_icon_placeholder').hide();
            $('#'+icon).css('display', 'inline-block');
            if (!icons.anims[icon].autoplay) icons.scroll();
        });
    }

    this.addEvents = function() {
        // json on hover
        $(document).on('mouseenter', '.bm_icon.hover', this.enter);
        $(document).on('mouseleave', '.bm_icon.hover', this.leave);
        // scroll
        $(window).scroll(this.scroll);
        //this.scroll(); // already gets called via this.resize()
        // resize
        $(window).resize(this.resize);
        this.resize();
    }

    this.resize = function () {
        // console.log("resizing");
        for (var i in icons.bgs) {
            if (icons.bgs[i].resize) {
                // console.log("background resizing");
                var bg = icons.bgs[i].container;
                // var margin = $(bg).parent().css('margin-left');
                // if (margin != "0px") margin = '-'+margin;
                var margin = -($(bg).parent()[0].getBoundingClientRect().left);
                margin += $('body')[0].getBoundingClientRect().left;
                
                //if the margin is greater than half the window-width, chances are the bg sits outside the viewport
                if ( Math.abs(parseInt(margin)) * 2 <= window.innerWidth ) {
                    if ( Math.abs(parseInt(margin)) !== 0 ) {
                        $(bg).css('left', margin);
                        $(bg).css('right', margin);
                    }
                }
            }
            if (icons.bgs[i].video) {
                icons.resizeIframe(i);
            }
        }
        icons.scroll();
    }
    this.resizeIframe = function(i) {
        // console.log(icons.bgs[i].video);
        var bg = icons.bgs[i].container;
        var pheight = $(bg).css('height').replace("px","");
        var pwidth = $(bg).css('width').replace("px","");
        
        var ar = icons.bgs[i].video.video_aspect.split("/");
        ar = ar[0]/ar[1]; // ar = 16/9;
        var height = pheight;
        var width = height * (ar);
        var margin_left = margin_top = 0;

        if (width >= pwidth) {
            margin_left = (pwidth - width) / 2;
        }
        else {
            width = pwidth;
            height = width / (ar);
            margin_top = (pheight - height) / 2;
        }

        // resize
        var iframe = typeof icons.bgs[i].video.container_id === 'undefined' ? $(bg).find('iframe') : $("#"+icons.bgs[i].video.container_id);
        console.log(iframe);
        if ( iframe && iframe.length ) {
            iframe.css('height', height+"px");
            iframe.css('width', width+"px");
            iframe.css('margin-left', margin_left+"px");
            iframe.css('margin-top', margin_top+"px");
        }
        else {
            setTimeout( function() {
                iframe = $(bg).find('iframe');
                // console.log(iframe);
                icons.bgs[i].video.container_id = iframe.attr('id');
                iframe.css('height', height+"px");
                iframe.css('width', width+"px");
                iframe.css('margin-left', margin_left+"px");
                iframe.css('margin-top', margin_top+"px");
            }, 10 );
        }
    }
    this.scroll = function () {
        // console.log("scrolling");
        var is_mobile = ($('html').hasClass('ios') || $('html').hasClass('android'));
        var viewportTop = $(window).scrollTop();
        var viewportHeight = $(window).height();
        var viewportBottom = viewportTop + viewportHeight;
        var width = window.screen.width * window.devicePixelRatio;
        if ( typeof lottie_setup !== 'undefined' && lottie_setup.lazyload === 'true' ) {
            var ic = [];
            var vc_icon = [];
            $('.vc_icon').each(function() {
                var elementTop = $(this).offset().top - 50;
                var elementBottom = elementTop + $(this).outerHeight() + 100;
                if (elementBottom > viewportTop && elementTop < viewportBottom) {
                    // console.log("enter ");

                    if (typeof $(this).data('srcset') !== 'undefined') {
                        var srcset = $(this).data('srcset');
                        var newset = { 'src': $(this).attr('src'), 'newsrc': '' };
                        for (var i in srcset) {
                            if (i > width && newset.src != srcset[i]) {
                                newset.newsrc = srcset[i];
                                break;
                            }
                        }
                        if (newset.newsrc == '' && newset.src != srcset['full'])
                            newset.newsrc = srcset['full'];
                        if (newset.newsrc != '') {
                            if (!vc_icon.some( function(ic){ic.src === newset.src})) vc_icon.push( newset );
                            $(this).attr('src', newset.newsrc);
                        }
                    }
                    
                    $(this).removeClass('vc_icon');
                } 
            });
            $('.vc_bg_image, .bg_image').each(function() {
                if (!$(this).hasClass('visible')) {
                    var elementTop = $(this).offset().top - 50;
                    var elementBottom = elementTop + $(this).outerHeight() + 100;
                    if (elementBottom > viewportTop && elementTop < viewportBottom) {
                        // console.log("enter ");
                        if (typeof $(this).data('srcset') !== 'undefined') {
                            var srcset = $(this).data('srcset');
                            var newset = { 'src': $(this).css('background-image').replace('url("', '').replace('")',''), 'newsrc': '' };
                            for (var i in srcset) {
                                if (i > width && newset.src != srcset[i]) {
                                    newset.newsrc = srcset[i];
                                    break;
                                }
                            }
                            if (newset.newsrc == '' && newset.src != srcset['full'])
                                newset.newsrc = srcset['full'];
                            if (newset.newsrc != '') {
                                if (!vc_icon.some( function(ic){ic.src === newset.src} )) vc_icon.push( newset );
                                $(this).css('background-image', 'url('+newset.newsrc+')');
                            }
                        }
                        $(this).addClass('visible');
                    }
                } 
            });
            if (vc_icon.length > 0) {
                // console.log(vc_icon);
                $('.vc_icon').each(function() {
                    for (var i=0; i < vc_icon.length; i++) {
                        if (vc_icon[i].src == $(this).attr('src')) {
                            $(this).attr('src', vc_icon[i].newsrc);
                            $(this).removeClass('vc_icon');
                            break;
                        }
                    }
                });
                $('.vc_bg_image, .bg_image').each(function() {
                    if (!$(this).hasClass('visible')) {
                        var src = decodeURI($(this).css('background-image').replace('url("', '').replace('")',''));
                        for (var i=0; i < vc_icon.length; i++) {
                            if (vc_icon[i].src == src) {
                                $(this).css('background-image', 'url('+vc_icon[i].newsrc+')');
                                $(this).addClass('visible');
                                break;
                            }
                        } 
                    }
                });
            }
        }

        var ic = [];
        for (var i in icons.anims) {
            if (icons.anims[i].status == "lazy") {
                var anim = '#'+i;
                var elementTop = $(anim).parent().offset().top - 50;
                var elementBottom = elementTop + $(anim).parent().outerHeight() + 100;
                if (elementBottom > viewportTop && elementTop < viewportBottom) {
                    ic.push(icons.anims[i].icon_path);
                    icons.loadIcon(icons.anims[i]);
                }
            }
            else if (!icons.anims[i].autoplay) {
                var anim = icons.anims[i].wrapper;
                var elementTop = $(anim).offset().top;
                var elementBottom = elementTop + $(anim).outerHeight();
                if (elementBottom > viewportTop && elementTop < viewportBottom) {
                    if (!$(anim).data('visible') || $(anim).data('visible') == false) {
                        $(anim).data('visible', true);
                        // console.log("enter "+i);
                        icons.anims[i].play();
                    }
                } 
                else {
                    if ($(anim).data('visible') == true) {
                        $(anim).data('visible', false);
                        // console.log("leave "+i);
                        icons.anims[i].pause();
                    }
                }
            }
        }
        if (ic.length > 0) {
            // console.log(ic);
            for (var i in icons.anims) {
                // console.log(icons.anims[i]);
                if (icons.anims[i].status == "lazy") {
                    for (var j=0; j < ic.length; j++) {
                        // console.log(ic[j]);
                        if (icons.anims[i].icon_path == ic[j]) {
                            // console.log("found one more");
                            icons.loadIcon(icons.anims[i]);
                            break;
                        }
                    }
                }
            }
        }
        for (var i in icons.bgs) {
            // all backgrounds
            if (icons.bgs[i].scroll.length > 0) {
                for (var j=0; j<icons.bgs[i].scroll.length; j++) {
                    // all parallax layers
                    var bg =icons.bgs[i].scroll[j].container;
                    var scroll =icons.bgs[i].scroll[j].scroll;
                    var enable_mobile = $(bg).data('vc_bg_parallax_enable_mobile');
                    if (!is_mobile || (is_mobile && enable_mobile)) {
                        // enable on desktop and check mobile
                        var elementTop = $(bg).offset().top;
                        var elementHeight = $(bg).outerHeight();
                        var speed = $(bg).data('vc_bg_parallax_speed');
                        if (viewportBottom-elementTop > 0 && viewportTop-elementTop < elementHeight) {
                            // console.log(viewportBottom-elementTop);
                            var delta = (viewportHeight - elementHeight) / 2;
                            var pos = ((viewportBottom-elementTop-elementHeight-delta)*(speed/100.0));
                            if (scroll == "vparallax") {
                                // vertical parallax
                                if ($(bg).hasClass('div_parallax')) 
                                    $(bg).children(0).css('transform', 'translateY('+pos.toFixed(2)+'px)');
                                else 
                                    $(bg).css('background-position-y', pos.toFixed(2)+'px');
                            }
                            if (scroll == "hparallax") {
                                // horizontal parallx
                                if ($(bg).hasClass('div_parallax'))
                                    $(bg).children(0).css('transform', 'translateX('+pos.toFixed(2)+'px)');
                                else
                                    $(bg).css('background-position-x', pos.toFixed(2)+'px');
                            }
                        }
                    }
                }
            }
        }
    }
	this.enter = function(e) {
        var icon = e.currentTarget.id;
		// console.log("enter "+icon);
		if (!icons.anims[icon].autoplay)
			icons.anims[icon].play();
	}
	this.leave = function(e) {
        var icon = e.currentTarget.id;
		// console.log("leave "+icon);
		if (!icons.anims[icon].autoplay)
			icons.anims[icon].pause();
	}

}

var custom_inputs = new function() {

    this.init = function() {
        this.initRadioCheckboxes();
        this.initSelects();
        this.initMultisselects();
        this.initTooltipsDeprecated();
    }
        
    /**
     * Custom radio buttons & checkboxes
     */
    this.initRadioCheckboxes = function() {
        var cbs = document.getElementsByTagName("input");
        for (var i = 0; i < cbs.length; i++) {
            // console.log(cbs[i]);
            if (
                (cbs[i].type == "checkbox" && cbs[i].style.display != 'none' && cbs[i].id != 'toggle' && cbs[i].closest('form.greyd_form') !== null) ||
                (cbs[i].type == "radio" && cbs[i].style.display != 'none')
            ) {
                var cb = cbs[i];
                // console.log(cb);
                var lbl = document.createElement("span");
                if (cb.hasAttribute("title")) lbl.setAttribute("title", cb.getAttribute("title"));
                cb.parentNode.insertBefore(lbl, cb.nextSibling);
            }
        }
    }
        
    /**
     * Custom selects
     */
    this.initSelects = function() {
        var x, i, j, selElmnt, a, b, c;
        // look for any elements with the class "custom-select":
        x = document.getElementsByClassName("custom-select");
        // console.log(x);
        // x = document.getElementsByTagName("select");
        for (i = 0; i < x.length; i++) {
            // console.log(x[i]);
            selElmnt = x[i].getElementsByTagName("select")[0];
            if (typeof selElmnt === 'undefined') continue;
            var t = x[i].getElementsByClassName("select-selected")[0];
            if (typeof t !== 'undefined') continue;
            // selElmnt = x[i];
            // for each element, create a new DIV that will act as the selected item:
            a = document.createElement("DIV");
            a.setAttribute("class", "animate_fast select-selected");
            if (selElmnt.hasAttribute("style")) a.setAttribute("style", selElmnt.getAttribute("style"));
            if (selElmnt.hasAttribute("title")) a.setAttribute("title", selElmnt.getAttribute("title"));
            a.setAttribute("onmouseover", selElmnt.getAttribute("onmouseover"));
            a.setAttribute("onmouseout", selElmnt.getAttribute("onmouseout"));
            a.innerHTML = selElmnt.options[selElmnt.selectedIndex].innerHTML;
            // x[i].appendChild(a);
            selElmnt.parentNode.insertBefore(a, selElmnt.nextSibling);
            // x[i].parentElement.appendChild(a);
            // for each element, create a new DIV that will contain the option list:
            b = document.createElement("DIV");
            b.setAttribute("class", "select-items select-hide");
            b.style.marginTop = '-'+selElmnt.style.marginBottom;
            b.style.fontSize = selElmnt.style.fontSize;
            b.style.fontWeight = selElmnt.style.fontWeight;
            b.style.color = selElmnt.style.color;
            b.style.background = selElmnt.style.background;
            b.style.borderRadius = selElmnt.style.borderRadius;
            b.style.borderColor = selElmnt.style.borderColor;
            b.style.borderWidth = selElmnt.style.borderWidth;
            b.style.borderLeftStyle = selElmnt.style.borderLeftStyle;
            b.style.borderRightStyle = selElmnt.style.borderRightStyle;
            b.style.borderTopStyle = selElmnt.style.borderTopStyle;
            b.style.borderBottomStyle = selElmnt.style.borderBottomStyle;

            // for each option in the original select element...
            for (j = 0; j < selElmnt.length; j++) {
                // create a new DIV that will act as an option item:
                c = document.createElement("DIV");
                c.setAttribute("class", "animate_fast");
                c.innerHTML = selElmnt.options[j].innerHTML;
                c.dataset.value = selElmnt.options[j].getAttribute('value');

                // custom css and hover
                if (selElmnt.getAttribute("onmouseover") && selElmnt.getAttribute("onmouseout")) {
                    c.style.color = selElmnt.style.color;
                    c.style.background = selElmnt.style.background;
                    c.style.paddingLeft = selElmnt.style.paddingLeft;
                    c.style.paddingRight = selElmnt.style.paddingRight;
                    c.style.paddingTop = "5px";
                    c.style.paddingBottom = "5px";
                    // make onmouseover
                    var t = selElmnt.getAttribute("onmouseover");
                    t = t.split(' !important').join('');
                    if (t.indexOf('color: ') > -1) {
                        t = t.split('color: ');
                        var color = t[1].split(';');
                        var jscolor = "$(this).css('color','"+color[0]+"');";
                        t = t[1];
                    }
                    if (t.indexOf('background: ') > -1) {
                        t = t.split('background: ');
                        var background = t[1].split(';');
                        var jsbackground = "$(this).css('background','"+background[0]+"');";
                    }
                    // c.setAttribute("onmouseover", "$(this).css('color','"+color[0]+"');$(this).css('background','"+background[0]+"');");
                    c.setAttribute("onmouseover", jscolor+jsbackground);
                    // make onmouseout
                    var t = selElmnt.getAttribute("onmouseout");
                    t = t.split(' !important').join('');
                    if (t.indexOf('color: ') > -1) {
                        t = t.split('color: ');
                        var color = t[1].split(';');
                        var jscolor = "$(this).css('color','"+color[0]+"');";
                        t = t[1];
                    }
                    if (t.indexOf('background: ') > -1) {
                        t = t.split('background: ');
                        var background = t[1].split(';');
                        var jsbackground = "$(this).css('background','"+background[0]+"');";
                    }
                    // c.setAttribute("onmouseout", "$(this).css('color','"+color[0]+"');$(this).css('background','"+background[0]+"');");
                    c.setAttribute("onmouseout", jscolor+jsbackground);
                }

                // when an item is clicked, update the original select box
                c.addEventListener("click", function(e) {
                    const select = this.parentNode.parentNode.getElementsByTagName("select")[0];
                    const index = [...this.parentElement.children].indexOf( this );
                    select.selectedIndex = index;
                    select.dispatchEvent(new Event('change'));
                });
                b.appendChild(c);
            }
            selElmnt.parentNode.insertBefore(b, a.nextSibling);

            // when the select box is clicked, close any other select boxes,
            a.addEventListener("click", function(e) {
                // and open/close the current select box:
                e.stopPropagation();
                custom_inputs.closeAllSelect(this);
                // this.nextSibling.classList.toggle("select-hide");
                this.classList.toggle("select-arrow-active");
                $(this.nextSibling).toggleClass("select-hide");
            });

            // change the fake select when changing the real select
            selElmnt.addEventListener("change", function (e) {
                const select = this;
                const fakeSelect = select.parentNode.getElementsByClassName('select-selected')[0];
                fakeSelect.innerHTML = select.options[select.selectedIndex].innerHTML;
                const fakeOptions = select.parentNode.getElementsByClassName('select-items')[0];
                const selectedOptions = fakeOptions.getElementsByClassName("same-as-selected");
                selectedOptions.length && selectedOptions[0].classList.remove("same-as-selected");
                fakeOptions.children[ select.selectedIndex ].classList.add("same-as-selected");
            })
        }
        // if the user clicks anywhere outside the select box,
        // then close all select boxes:
        document.addEventListener("click", custom_inputs.closeAllSelect);
    }
    
    /**
     * a function that will close all select boxes in the document,
     * except the current select box
     */
    this.closeAllSelect = function(elmnt) {
        var x, y, i, arrNo = [];
        x = document.getElementsByClassName("select-items");
        y = document.getElementsByClassName("select-selected");
        z = document.getElementsByClassName("greyd_multiselect");
        for (i = 0; i < y.length; i++) {
            if (elmnt == y[i]) {
                arrNo.push(i)
            } else {
                y[i].classList.remove("select-arrow-active");
            }
        }
        for (i = 0; i < x.length; i++) {
            if (arrNo.indexOf(i)) {
                x[i].classList.add("select-hide");
            }
        }
        for (i = 0; i < z.length; i++) {
            z[i].classList.remove("open");
        }
    }

	/**
	 * GREYD Multiselect
	 *
	 * @deprecated since 1.7.0
	 * can be removed in future versions
	 */
    this.initMultisselects = function() {
        $(".greyd_form select[multiple]").each(function() {
            custom_inputs.renderMultiselect(this);
        });
        
        $(".greyd_multiselect").each(function() {
            var wrap = $(this);
            var input = wrap.find(".input");
            
            // open the dropdown
            input.off("click").on("click",  function(e) {
                e.stopPropagation();
                var open = wrap.hasClass("open") ? false : true;
                $(".greyd_multiselect").each(function() { $(this).removeClass("open"); });
                custom_inputs.closeAllSelect();
                if (open) {
                    wrap.addClass("open");
                    $(document).one("click", function() {
                        wrap.removeClass("open");
                    });
                }
            });
            
            // toggle tag
            var option = wrap.find(".option");
            option.off("click").on("click",  function() {
                custom_inputs.toggleMultiselectValue(this);
            });

            // control multiselects with keyboard
            document.addEventListener( "keydown", e => {
            
                const multiselectWrapper = e.target?.closest(".greyd_multiselect");
                const input = multiselectWrapper?.querySelector("input");

                if ( !multiselectWrapper || !input ) return;

                const fakeSelect = multiselectWrapper?.querySelector(".input");
                const dropdown = multiselectWrapper?.querySelector(".dropdown");
                
                if ( multiselectWrapper?.classList?.contains("open") ) {
                    if (e.key == "ArrowUp" || e.key == "ArrowDown" ) {
                        e.preventDefault();
                        custom_inputs.focusNextMultiselectItem(e);
                    } else if (e.key == " " && e.target.classList.contains("option")) {
                        const option = e.target;
                        e.preventDefault();
                        custom_inputs.toggleMultiselectValue(option);
                    } else if (e.key == "Escape") {
                        custom_inputs.closeAllSelect();
                    }
                } else {
                    if (e.key == "Enter") {
                        e.preventDefault();
                        multiselectWrapper.classList.add("open");
                    }
                }
            });
        });
    }
    
    this.toggleMultiselectValue = function(elem) {
        event.stopPropagation();
        elem = $(elem);
        if ( elem.hasClass('selected') )
            this.removeMultiselectValue(elem);
        else
            this.addMultiselectValue(elem);
    }
    
    this.removeMultiselectValue = function(elem) {
        event.stopPropagation();
        elem = $(elem);
        var val     = elem.data('value');
        var wrap    = elem.closest('.greyd_multiselect');
        
        // reset option
        wrap.find('.dropdown .option[data-value="'+val+'"]').removeClass('selected');
        
        // remove tag
        wrap.find('.input .tag[data-value="'+val+'"]').remove();
        
        // remove value
        var field = wrap.find('input');
        var values = field.val().split(',');
        values = values.filter(function(v, i) {
            if (v != "" && v != val && values.indexOf(v) == i) return true;
        });
        custom_inputs.setMultiselectValue( field, values.join(',') );
        // custom_inputs.setMultiselectValue( field, field.val().replace(val, "") );
    }
    
    this.addMultiselectValue = function(elem) {
        event.stopPropagation();
        elem = $(elem);
        var val     = elem.data('value');
        var name    = elem.text();
        var wrap    = elem.closest('.greyd_multiselect');
        var input   = wrap.find('.input');
        
        // highlight option
        wrap.find('.dropdown .option[data-value="'+val+'"]').addClass('selected');
        
        // append tag
        // var tag     = input.data('tag');
        var tag     = decodeURIComponent(input.data('tag'));
        tag = tag.replace("{value}", val).replace("{name}", name);
        input.find('.tags').append(tag);
        
        // add value
        var field = wrap.find('input');
        var values = field.val().split(',');
        values.push(val);
        values = values.filter(function(v, i) {
            if (v != "" && values.indexOf(v) == i) return true;
        });
        custom_inputs.setMultiselectValue( field, values.join(',') );
        // custom_inputs.setMultiselectValue( field, field.val()+","+val );
    }
    
    this.setMultiselectValue = function(field, value) {
        // cleanup double commas or commas at the end or beginning of the string
        // field.val( value.replace(/,+/, ",").replace(/^,|,$/, "") );
        field.val( value );
        field.trigger("change");
    }
    
    this.renderMultiselect = function(select) {
        var placeholder = '';
        var options     = {};
        var values      = [];
        var name        = select.getAttribute('name');
        var className   = select.getAttribute('class');
        var required    = select.hasAttribute('required');
        var tag_code    = "<span class=\"tag\" data-value=\"{value}\" onclick=\"custom_inputs.removeMultiselectValue(this);\">{name}<span class=\"icon_close\"></span></span>";
        
        $(select).children('option').each(function() {
            var txt = this.textContent;
            var val = this.hasAttribute('value') ? this.getAttribute('value') : '';
            if ( val == '' && placeholder == '' ) {
                placeholder = txt;
            } else {
                val = val == '' ? txt : val;
                options[val] = txt;
                if ( this.hasAttribute('selected') )
                    values.push(val);
            }
        });
        
        // create wrapper
        var wrap = document.createElement("div");
        wrap.setAttribute("class", "greyd_multiselect");
        select.after(wrap);
        
        // remove original select
        select.parentNode.removeChild(select);
        
        // create input
        var input = document.createElement("input");
        input.setAttribute("type", "text");
        input.setAttribute("name", name);
        input.setAttribute("value", values.join(',') );
        input.setAttribute("tabindex", -1 );
        if ( required ) input.setAttribute("required", "required");
        wrap.appendChild(input);
        
        // create div.input
        var div = document.createElement("div");
        div.setAttribute("class", "input"+( className ? ' '+className : '' ));

        // make it tabbable
        div.setAttribute("role", "listbox");
        div.setAttribute("tabindex", 0);
        div.setAttribute("aria-multiselectable", "true");
        div.setAttribute("data-tag", encodeURIComponent(tag_code));
        wrap.appendChild(div);
        
        // create tags
        var tags = document.createElement("span");
        tags.setAttribute("class", "tags");
        tags.setAttribute("data-placeholder", placeholder);
        div.appendChild(tags);
        if ( values.length > 0 ) {
            values.forEach(function (value, i) {
                var tag = $.parseHTML( tag_code.replace( /{value}/, value ).replace( /{name}/, options[value] ) );
                $(tags).append( tag );
            });
        }
        
        // create dropdown
        var dropdown = document.createElement("div");
        dropdown.setAttribute("class", "dropdown");
        wrap.appendChild(dropdown);
        for (var val in options) {
            
            // create option
            var option = document.createElement("div");
            option.setAttribute("class", "option"+( values.indexOf(val) >= 0 ? " selected" : "" ) );
            option.setAttribute("data-value", val);

            // declare it as listbox child
            option.setAttribute("role", "option");
            option.setAttribute("tabindex", 0);
            dropdown.appendChild(option);
            
            var icon = document.createElement("span");
            icon.setAttribute("class", "icn" );
            option.appendChild(icon);
            
            option.innerHTML += options[val];
        }
    }

    /**
     * Helper function for multiselects to switch between options with ArrowUp & Arrow Down
     * @param {event}
     */
    this.focusNextMultiselectItem = (e) => {
        const key = e.key;
        const target = e.target;
        const activeElement = document.activeElement;

        const selectWrapper = target.closest(".greyd_multiselect");
        const selectGroup = selectWrapper.querySelector(".dropdown");
        const items = selectGroup.children;

        if ( activeElement.classList.contains("input") && selectWrapper.classList.contains("open") && key == "ArrowDown") {
            items[0].focus();
        } else if ( activeElement.nextSibling && key == "ArrowDown") {
            activeElement.nextSibling.focus();
        } else if ( activeElement.previousSibling && key == "ArrowUp") {
            activeElement.previousSibling.focus();
        } else return;
    }

    /**
     * Init form tooltips
	 *
	 * @deprecated since 1.7.0
	 * can be removed in future versions
     */
    this.initTooltipsDeprecated = function() {
			
		// check if new version is already initialized
		if ( typeof forms !== 'undefined' && typeof forms.initFormTooltips !== 'undefined' ) return;

        if ( $( '.forms-tooltip' ).length == 0 ) return;

        var activeTooltip = null;
        
        $( document ).on( "click", () => {
            if ( activeTooltip ) {
                activeTooltip.removeClass( 'on' );
                activeTooltip = null;
            }
        } );

        // toggle tooltip
        $( ".forms-tooltip .forms-tooltip-toggle" ).on( "click", ( e ) => {
            let tooltip = $( e.target ).parent();
            if ( tooltip.hasClass( 'on' ) ) {
                if ( activeTooltip ) {
                    activeTooltip.removeClass( 'on' );
                }
                activeTooltip = null;
            } else {
                if ( activeTooltip ) {
                    activeTooltip.removeClass( 'on' );
                }
                activeTooltip = tooltip;
            }
            tooltip.toggleClass( 'on' );
            e.stopPropagation();
        } );
    }
}

var content_wrapper = new function() {

    this.init = function() {
        $(document).on('click', '.cwp_button .button', content_wrapper.open);
        $(document).on('mousewheel', function() { $('html, body').stop(); });
    }
    this.open = function() {
        var wrapper = $(this).closest('.cwp');
        var speed = $('.navigation').data('mod-scrollspeed')*1000;
        var nav = $('.navigation').height();
        var pos = wrapper.offset().top;
        var np  = pos - nav - 100;
        wrapper.find('.vc_inner').slideDown(speed, function() {
            var speed = $('.navigation').data('mod-anim')*1000;
            $('html, body').stop().animate({ scrollTop: np }, speed);
            wrapper.find('.cwp_button .button').fadeOut();
        });
    }

}

/**
 * todo: refactor to query
 */
var posts = new function() {
    
    this.init = function() {
        this.addEvents();
        // console.log('Posts Pagination Script: loaded');
    }
    
    this.addEvents = function() {
        var wrapper = $(".posts_wrapper.js");
        if (wrapper.length > 0) {

            wrapper.each(function() {
                if ($(this).hasClass('autoplay')) {
                    var time = $(this).data('interval')*1000;
                    var that = this;
                    $(this).timer = setInterval(function() {
                        if (!$(that).hasClass('pause') && !$(that).hasClass('forcePause')) {
                            posts.paginateNext($(that));
                        }
                    }, time);
                    // $(this).on('mouseenter', function() {
                    //     if ( !$(that).hasClass('pause') ) {
                    //         $(that).addClass('pause');
                    //         $(this).on('mouseleave', function() {
                    //             $(that).removeClass('pause');
                    //         });
                    //     }
                    // });
                }
                
                $(this).off('next').on('next', function() {
                    posts.paginateNext(this);
                });
                $(this).off('prev').on('prev', function() {
                    posts.paginatePrevious(this);
                });
                $(this).off('first').on('first', function() {
                    posts.paginate(this, 1);
                });
                $(this).off('last').on('last', function() {
                    var pages = $(this).find('.result_wrapper');
                    posts.paginate(this, pages.length);
                });
                $(this).off('stop').on('stop', function() {
                    $(this).addClass('forcePause pause');
                });
                $(this).off('start').on('start', function() {
                    $(this).removeClass('forcePause pause');
                });
            });

            // next/previous
            $('.posts_wrapper.js .pagination > a, .posts_wrapper.js a.pgn_arrows, .posts_wrapper.js .pgn_arrows a').off('click').on("click", function(e) {
                var wrapper = $(this).closest('.posts_wrapper');
                wrapper.trigger( $(this).data('pagelink') );
                wrapper.trigger('stop');
            });
            
            // numbers
            $('.posts_wrapper.js .pagination > .pgn_numbers > a').off('click').on("click", function(e) {
                var wrapper = $(this).closest('.posts_wrapper');
                posts.paginate( wrapper, $(this).data('pagelink') );
                wrapper.trigger('stop');
            });

            // touch support
            $('.posts_wrapper.js').off('touchstart').on('touchstart', function(e) { 
                posts.touchStart(this, e);
                $(this).trigger('stop');
            });
            $('.posts_wrapper.js').off('touchmove').on('touchmove', function(e) {
                posts.touchMove(this, e);
                $(this).trigger('stop');
            });

            // size wrapper
            window.addEventListener("resize", function() { 
                posts.calc()
            });
            setTimeout(function(){
                posts.calc(true)
            }, 100);
            
            $(document).keydown(function(e){
                // arrow left
                if (e.keyCode == 37) {
                    var wrapper = $(".posts_wrapper.js:hover");
                    if ( wrapper.length > 0 ) {
                        wrapper.trigger('prev');
                        wrapper.trigger('stop');
                    }
                    return false;
                }
                // arrow right
                if (e.keyCode == 39) {
                    var wrapper = $(".posts_wrapper.js:hover");
                    if ( wrapper.length > 0 ) {
                        wrapper.trigger('next');
                        wrapper.trigger('stop');
                    }
                    return false;
                }
            });
        }
    }

    this.calc = function( onLoad ) {
        $('.posts_wrapper.js .results_wrapper').each(function(e) {
            var pages   = $(this).find('.result_wrapper');
            var wrapper = $(this).closest('.posts_wrapper');
            var current = wrapper.data('currentpage');
            var isBlockSlider = wrapper.hasClass('greyd-posts-slider');

			if ( typeof onLoad === 'undefined' || !onLoad ) {
				var offset  = pages[current-1].offsetLeft;
				if ( !isBlockSlider ) offset -= pages[current-1].parentElement.offsetLeft; /** @deprecated 1.0 */

				// console.log(offset);
				$(this).finish().animate({ scrollLeft: offset }, 0);
			}
            
            // get slider height
            var height = $(this)[0].offsetHeight+"px";
            var newHeight = null;

            /**
             * Adjust the slider height.
             * 
             * The naming of the options are misleading:
             * (1) 'auto'   =>  'as high as highest slide'
             *                  This is called auto, because the height of the slider
             *                  is automatically this way due to 'display: flex'. We
             *                  do not need to do anything in JS.
             * (2) 'max'    =>  'adjust automatically'
             *                  We need to adjust the slider's height to the height
             *                  of the current slide each step.
             * (3) 'custom' =>  'manual height'
             *                  Set the height to a manual input.
             */
            if ( wrapper.data('height') == 'auto' ) {
                // (1)
            }
            else if ( wrapper.data('height') === 'max') {
                // (2)
                newHeight = pages.length > 1 ? pages[current-1].offsetHeight+"px" : null;
            }
            else {
                // (3)
                newHeight = wrapper.data('height');
                if ( screen.width <= 767 && typeof wrapper.data('height_mobile') != 'undefined' && wrapper.data('height_mobile') != 'max' ) {
                    newHeight = wrapper.data('height_mobile');
                }
            }

            // set height for new slider
            if (newHeight) {
                height = newHeight;
                $(this).css('height', height);
            }


            /**
             * Bugfix: <p>-tag around arrows leads to display errors
             * 
             * If a post-overview is rendered dynamically inside a vc_text-module, the function 'wpautop'
             * is used to render the content. This creates a <p>-tag wrapped around the arrow-elements.
             * Therefore the arrows are no longer direct siblings of $(this).
             * 
             * @since 0.8.6
             * @deprecated 1.0
             */
            if ( !isBlockSlider ) {
                var arrows = $(this).siblings('.pgn_arrows');
                if (arrows.length == 0) {
                    arrows = $(this).siblings('p').length > 0 ? $(this).siblings('p').children('.pgn_arrows') : arrows;
                }
                if (arrows.length > 0) {
                    // console.log("setting height calc");
                    // console.log(arrows);
                    $(arrows).css('height', height);
                    // var prev = $(this).closest('.posts_wrapper').find('.pgn_previous');//.children()[0];
                    // var next = $(this).closest('.posts_wrapper').find('.pgn_next');//.children()[2];
                    // $(prev).parent().css('height', height);
                    // $(next).parent().css('height', height);
                }
            }
        });
    }

    this.paginateNext = function(wrapper) {
        // console.log("next page");
        var newpage = $(wrapper).data('currentpage')+1;
        this.paginate(wrapper, newpage);
    }
    this.paginatePrevious = function(wrapper) {
        // console.log("previous page");
        var newpage = $(wrapper).data('currentpage')-1;
        this.paginate(wrapper, newpage);
    }
    this.paginate = function(wrapper, newpage) {

        var isBlockSlider = $(wrapper).hasClass('greyd-posts-slider');
        var current = $(wrapper).data('currentpage');
        var pages = $(wrapper).find('.result_wrapper');//.children();
        // console.log("current page: "+current);
        // console.log("new page: "+newpage);
        // console.log(pages);
        if (current == newpage) return;
        if ($(wrapper).hasClass('loop')) {
            if (newpage < 1) newpage = pages.length;
            if (newpage > pages.length) newpage = 1;
        }
        else {
            if (newpage < 1) return;
            if (newpage > pages.length) return;
        }

        var resultsWrapper = $(wrapper).find('.results_wrapper');
        var activeStep = pages[newpage-1];
        
        $(wrapper).data('currentpage', newpage);
        pages.removeClass('is-current is-prev is-next');
        $(pages[newpage-2]).addClass('is-prev');
        $(activeStep).addClass('is-current');
        $(pages[newpage]).addClass('is-next');

        // offset
        var offset = activeStep.offsetLeft;
        if ( !isBlockSlider ) {
            /** @deprecated 1.0 */
            offset -= activeStep.parentElement.offsetLeft;
        }

        // speed
        var speed = $('.navigation').data('mod-anim')*1000;
        if ( $(wrapper).data('animation') == 'none' ) {
            speed = 0;
        }

        var height = pages.offsetHeight+"px"; // default height
        var prev = $(wrapper).find('.pgn_previous');//.children()[0];
        var next = $(wrapper).find('.pgn_next');//.children()[2];
        
        // custom height
        if ($(wrapper).data('height') != 'auto') {
            if ($(wrapper).data('height') === 'max') {
                height = activeStep.offsetHeight+"px";
                // if (pages.length > 1) {
                //     var highest = null;
                //     var hi = 0;
                //     pages.each(function(){
                //         var h = $(this).height();
                //         if(h > hi){
                //             hi = h;
                //             highest = $(this);  
                //         }    
                //     });
                //     if (highest) height = highest.height();
                // } else {
                //     height = null;
                // }
            } else {
                height = $(wrapper).data('height');
                if ( screen.width <= 767 && typeof $(wrapper).data('height_mobile') != 'undefined' && $(wrapper).data('height_mobile') != 'max' )
                    height = $(wrapper).data('height_mobile');
            }
        }
        
        /**
         * Set pagination height
         * @deprecated 1.0
         */
        if ( !isBlockSlider && $(wrapper).data('height') === 'max' ) {
            var arrows = $(wrapper).find('.pgn_arrows');
            if (arrows.length > 0) {
                // console.log("setting height paginate");
                // console.log(arrows);
                $(arrows).css('height', height);
            }
            // if ($(prev).parent().hasClass('pgn_arrows')) {
            //     $(prev).parent().css('height', height);
            //     $(next).parent().css('height', height);
            // }
        }

        // animate slider
        if ($(wrapper).data('animation') == 'fade') {
            $(resultsWrapper).finish().animate({ opacity: 0 }, speed);
            setTimeout(function() {
                $(resultsWrapper).animate({ scrollLeft: offset }, 0);
                $(resultsWrapper).finish().animate({ opacity: 1, height: height }, speed);
            }, speed);
        }
        else if ($(wrapper).data('animation') == 'cover') {
            // the cover-flow animation is entirely handled by CSS
            $(resultsWrapper).finish().animate({ height: height }, speed, 'swing');
        }
        else {
            $(resultsWrapper).finish().animate({ scrollLeft: offset, height: height }, speed, 'swing');
        }

        // scroll to top
        if ( $(wrapper).hasClass('slider_scroll_top') ) {
            var offsetTop = $(wrapper).offset().top - 100;
            if ( $(document).scrollTop() > offsetTop ) {
                nav.scrollTo( offsetTop );
            }
        }
        
        // pagination
        var pg_wrapper = $(wrapper).find('.pagination');
        if ( pg_wrapper.length ) {
            var pg_pages = $(pg_wrapper).find('.pgn_numbers').children();
            var iconnormal = $(pg_wrapper).data('iconnormal') ?? $(pg_wrapper).find('.pgn_numbers').data('iconnormal');
            var iconactive = $(pg_wrapper).data('iconactive') ?? $(pg_wrapper).find('.pgn_numbers').data('iconactive');
            var imgnormal = $(pg_wrapper).data('imgnormal') ?? $(pg_wrapper).find('.pgn_numbers').data('imgnormal');
            var imgactive = $(pg_wrapper).data('imgactive') ?? $(pg_wrapper).find('.pgn_numbers').data('imgactive');
    
            for (var i=0; i<pg_pages.length; i++) {
                // console.log("pagination ");
                // console.log($(pg_pages[i]));
                var p = $(pg_pages[i]);
                if (!$(p).hasClass('pgn_number')) p = $(p).find('.pgn_number');
                if ($(pg_pages[i]).data('pagelink') == current) {
                    $(p).removeClass('pgn_current');
                    if (typeof iconactive !== 'undefined') $(p).removeClass(iconactive);
                    if (typeof iconnormal !== 'undefined') $(p).addClass(iconnormal);
                    if (typeof imgnormal !== 'undefined') $(p).children().attr('src', imgnormal);
                }
                if ($(pg_pages[i]).data('pagelink') == newpage) {
                    $(p).addClass('pgn_current');
                    if (typeof iconnormal !== 'undefined') $(p).removeClass(iconnormal);
                    if (typeof iconactive !== 'undefined') $(p).addClass(iconactive);
                    if (typeof imgactive !== 'undefined') $(p).children().attr('src', imgactive);
                }
            }
        }

        $(prev).removeClass('pgn_current');
        if (newpage == 1) $(prev).addClass('pgn_current');
        $(next).removeClass('pgn_current');
        if (newpage == pages.length) $(next).addClass('pgn_current');

        // trigger custom JS event
        $(wrapper).trigger("greyd_slider_paginate", [newpage] );
    }
     
    this.xDown = null; 
    this.touchStart = function(el, evt) {
        posts.xDown = evt.originalEvent.touches[0].clientX;
    }
    this.touchMove = function(el, evt) {
        if (!posts.xDown) {
            return;
        }
        var xUp = evt.originalEvent.touches[0].clientX;    
        var xDiff = posts.xDown - xUp;    
        if (xDiff > 50) {
            // console.log("scroll right");
            $(el).trigger('next');
            $(el).trigger('stop');
            posts.xDown = null; 
        }
        else if (xDiff < -50)  {
            // console.log("scroll left");
            $(el).trigger('prev');
            $(el).trigger('stop');
            posts.xDown = null; 
        }
    }
    
    this.triggerNext = function(el) {
        // console.log(el);
        $(el).closest('.wp-block-query').find('.posts_wrapper.js').trigger('next');
    }
    this.triggerPrevious = function(el) {
        // console.log(el);
        $(el).closest('.wp-block-query').find('.posts_wrapper.js').trigger('prev');
    }
    this.triggerIndex = function(el, index) {
        // console.log(el);
        $(el).closest('.wp-block-query').find('.posts_wrapper.js').each(function() {
            posts.paginate(this, index);
        });
    }
    
}

var announce = new function() {

    this.init = function() {
        if ($('.announce').length == 0) { 
            // console.log('stopped. No Announcement found.');
            return false; 
        }
        var preview = JSON.parse(sessionStorage.getItem('announcement_preview'));
        var data_name = 'announcement_'+$('.announce').data('announce-id');
        var data = theme.getLocal(data_name);
        if (data && !preview) {
            // console.log('stopped. data found.');
            $('.announce').remove();
        }
        else if ((preview && $('.announce').data('preview') == 'on') || $('.announce').data('preview') == 'off') {
            // console.log("no data found, open it.");
            $('body').addClass('announce_open');
            $(document).on('click', "div[data-close='announce']", announce.close );
        }
        // console.log('Announcement Script: loaded');
    }
    this.close = function() {
        $('body').removeClass('announce_open');
        var data_name = 'announcement_'+$('.announce').data('announce-id');
        theme.setLocal(data_name, { "set": new Date().getTime() } ); 
    }

}

/*
 * jq.TableSort -- jQuery Table sorter Plug-in
 *
 * Copyright (c) 2017 Dmitry Zavodnikov
 *
 * Licensed under the MIT License
 * 
 * todo: refactor to query
 */
var tablesort = new function() {
    
    this.init = function() {
        
        // return if not needed
        if (  $(".posts_table").length === 0 ) return false;

        this.config  = {
            styles: {
                'sort':     'sortable', 
                'asc':      'ascending', 
                'desc':     'descending', 
                'unsort':   'unsorted'
            },
            defaultColumn: 0,
            defaultOrder: 'asc',
            selector: function(tbody, column) {
                var groups = [];
                $.each($(tbody).find('tr'), function(index, tr) {
                    var td = $(tr).find('td')[column];
                    groups.push({
                        'elem': [tr], 
                        'text': $(td).text()
                    });
                });
                return groups;
            },
            comparator: function(a, b) {
                function convertToNum(x) { return parseFloat( x.replace(',','.') ); }
                var regex = /^[0-9\s.,]+$/;
                a = a.text, b = b.text;
                if ( a.match(regex) && b.match(regex) ) return convertToNum(b) - convertToNum(a);
                return a.localeCompare(b);
            }
        };
        
        $(".posts_table").each( function(i, wrapper) {
            // Add click listener to the header.
            $.each(tablesort.getSortableTableHeaders(wrapper), function(j, th) {
                $(th).off("click").on("click", function(event) {
                    var clickColumn = $.inArray(event.currentTarget, tablesort.getAllTableHeaders( $(this).closest("table") ));
                    
                    tablesort.changeOrder(wrapper, clickColumn);
                });
                // $(th).append("<span class='icon'><span></span><span></span></span>"); // already added via php
            });
            // Table sort on load
            //tablesort.changeOrder(table, tablesort.config.defaultColumn);
        });
    }
    
    this.getAllTableHeaders = function(wrapper) {
        return $(wrapper).find('thead > tr > th');
    }
    
    this.getNthTableHeaders = function(wrapper, n) {
        return $(wrapper).find('thead > tr > th:nth-child('+(n+1)+')');
    }

    this.getSortableTableHeaders = function(wrapper) {
        return tablesort.getAllTableHeaders(wrapper).filter(function(index){
            return $(this).hasClass(tablesort.config.styles['sort']);
        });
    }

    this.changeOrder = function(wrapper, column) {
        var th = tablesort.getNthTableHeaders(wrapper, column);
        
        // Order
        var sortOrder = tablesort.config.defaultOrder;
        if (th.hasClass(tablesort.config.styles['asc'])) {
            sortOrder = 'desc';
        }
        if (th.hasClass(tablesort.config.styles['desc'])) {
            sortOrder = 'asc';
        }
        
        // Reset
        var headers = tablesort.getSortableTableHeaders(wrapper);
        headers.removeClass( tablesort.config.styles['asc']+" "+tablesort.config.styles['desc'] );
        headers.addClass( tablesort.config.styles['unsort'] );
        
        // Set classes
        th.removeClass(tablesort.config.styles['unsort']);
        th.addClass(tablesort.config.styles[sortOrder]);
        
        // Group
        var tbody = $(wrapper).find('tbody');
        var groups = tablesort.config.selector(tbody, column);

        // Sorting
        groups.sort(function(a, b){
            var res = tablesort.config.comparator(a, b);
            return sortOrder == 'asc' ? res : -1 * res;
        });

        // Change order
        var rows = parseInt( $(wrapper).data('rows') ), i = 0, k = 0;
        $.each(groups, function(i, trList) {
            if ( i === rows * ( k+1 ) ) k++;
            $.each(trList.elem, function(j, tr) {
                tbody[k].append(tr);
            });
            i++;
        });
    }
};

/**
 * todo: refactor to query
 */
var sorting = new function() {

    this.init = function() {
        // return if not needed
        if ($(".posts_wrapper").length === 0) return false;
        
        // normal pages
        if (!($(".posts_wrapper.js").length === 0)) {
            this.addEvents();
        }
        // search page
        else {
            if ($('.posts_wrapper').attr("live-search") == "true") {
                return false
            } else {
                this.startQuery();
            }
        }
    }

    this.addEvents = function() {
        const sortingDropdown = greyd_builder == "gg-gg" ?  $('.posts_wrapper').find('.sorting') :  $('[id^="sorting_"]');
        sortingDropdown.each(function() {
            var wrapper = $(this);
            var select  = wrapper.find("select");
            var slider  = wrapper.siblings('[id^="slider_"]');
            if (wrapper.parent().hasClass('posts_wrapper')) slider = wrapper.parent();
            var results = slider.find($(".search_result"));

            // sort and redistribute search results
            select.off("change").on("change", function() {
                // console.log(slider);
                // console.log(results);
                var sorted_elements = sorting.sortElements($(this).val(), results);
                // console.log(sorted_elements);
                sorting.distributeElements(sorted_elements, slider);
            });
        });
        
        $('[id^="filter_"]').each(function() {
            var results = $(this).siblings('[id^="slider_"]').find($(".search_result"));
            if (results) {
                var select = $(this).find("select");
                var filter = select.val();
                $(results).each(function() {
                    sorting.filterElements(this, filter);
                });
                select.off("change").on("change", function() {
                    var filter = $(this).val();
                    $(results).each(function() {
                        sorting.filterElements(this, filter);
                    });
                });
            }
        });
    }

    this.filterElements = function(el, filter) {
        var vals = JSON.parse(decodeURIComponent($(el).data('filter')));
        filter = parseInt(filter);
        if (filter == null || filter == -1 || vals.indexOf(filter) != -1)
            $(el).css('display', 'block');
        else
            $(el).css('display', 'none');
    }

    this.sortElements = function(mode, search_results) {

        search_results.sort(function (a, b) {

            if (~mode.indexOf("title")) {
                // propably do that via php...
                // var compA = a.dataset.title.toLowerCase().replace(/[^a-zA-Z0-9 ]/g, "");
                // var compB = b.dataset.title.toLowerCase().replace(/[^a-zA-Z0-9 ]/g, "");
                var compA = a.dataset.title;
                var compB = b.dataset.title;
            }
            else if (~mode.indexOf("date")) {
                var compA = a.dataset.date;
                var compB = b.dataset.date;
            }
            else if (~mode.indexOf("views")) {
                // propably do that via php...
                // var compA = (a.dataset.postviews != 0) ? parseInt(a.dataset.postviews) : 0; 
                // var compB = (b.dataset.postviews != 0) ? parseInt(b.dataset.postviews) : 0;
                var compA = parseInt(a.dataset.postviews); 
                var compB = parseInt(b.dataset.postviews);
            }
            //compare
            if (~mode.indexOf("ASC")) {
                return ((compA < compB) ? -1 : ((compA > compB) ? 1 : 0));
            }
            else if (~mode.indexOf("DESC")) {
                return ((compA > compB) ? -1 : ((compA < compB) ? 1 : 0));
            }
        });  
        return search_results;
    }
    this.distributeElements = function(sorted_elements, parent) {
        var posts_per_page = $(".results_wrapper").data("ppp");
        var pages  = $(".result_wrapper").length;
        var i = 0;

        for (var x = 1; x <= pages; x++) {
            var limit_per_page = 1;
            for (; i < sorted_elements.length;) {
                
                parent.find($('.result_wrapper[data-page="' + x + '"]')).append(sorted_elements[i]);
                i++;
                if (limit_per_page == posts_per_page) {
                    break;
                }
                limit_per_page++;
            }                
        } 
    }
    this.startQuery = function() {
        const sortingDropdown = greyd_builder == "gg-gg" ?  $('.posts_wrapper').find('.sorting') :  $('[id^="sorting_"]');
        sortingDropdown.find("select").off("change").on('change', function () {
            var url = $(this).val(); // get selected value
            if (url) { // require a URL
                window.location = url; // redirect
            }
            return false;
        });
    }
}

var counter = new function() {
    
    this.objects = [];
    
    this.init = function() {
        
        if ( $('.greyd_counter').length === 0 ) return false;
        
        // push counters to array
        $('.greyd_counter').each(function() {
            var obj     = $(this).find('.count');
            var time    = parseFloat( obj.data('time') ) * 1000;
            
            if ( obj.length !== 0 && time > 0 ) counter.objects.push( obj );
        });
        
        // add animation trigger
        $(window).scroll(function() {
            counter.checkObjects();
        });
        counter.checkObjects();
    }
    
    this.checkObjects = function() {
        var i;
        for ( i = 0; i < counter.objects.length; ++i ) {
            if ( counter.objects[i] !== null && counter.isInViewport( counter.objects[i] ) ) {
                counter.animateValue( counter.objects[i] );
                counter.objects[i] = null;
            }
        }
    }
    
    /**
     * Check if an element is in the viewport.
     * This function returns true, if the object is at least in the top two thirds of the viewport.
     * @param {jQuery} el   Instance of an jQuery object.
     * @returns bool        Whether the element is in the viewport.
     */
    this.isInViewport = function(el) {
        // console.log( "top: "+el[0].getBoundingClientRect().top+", windowHeight: "+(window.innerHeight || document.documentElement.clientHeight) );
        return el[0].getBoundingClientRect().top <= ( (window.innerHeight || document.documentElement.clientHeight) * 0.66 );
    }
    
    this.animateValue = function(obj) {
        if ( obj.hasClass('animating') || obj.hasClass('animated') ) return;
        
        obj.addClass('animating');
        
        $({ Count: obj.text() }).delay(100).animate(
            { Count: obj.data('end') },
            {
                duration: parseFloat( obj.data('time') ) * 1000,
                easing: 'swing',
                step: function () {
                    obj.text(Math.ceil( this.Count ));
                },
                complete: function () {
                    obj.text(this.Count);
                    obj.removeClass('animating').addClass('animated');
                }
            }
        );
    }

    this.restartCounter = function(obj, newEnd) {
        obj.removeClass('animated animating');
        if (typeof newEnd !== 'undefined') obj.data('end', newEnd);
        counter.animateValue(obj);
    }
}

/**
 * deprecated but still used by VC content.
 * moved to Plugin and refactored to 'greyd.cssAnims'.
 * classes have changed in Blocks Rendering so the new functions are used instead.
 */
var vc_anim = new function() {
    
    this.objects = [];
    
    this.init = function() {
        // push counters to array
        $('.wpb_animate_when_almost_visible:not(.wpb_start_animation)').each(function() {
            var $el = $(this);
            if ( $el.length !== 0 ) {
                if ($el.hasClass('none')) $el.removeClass('wpb_animate_when_almost_visible');
                else vc_anim.objects.push( $el );
            }
        });
        // add animation trigger
        $(window).scroll(function() {
            vc_anim.checkObjects();
        });
        vc_anim.checkObjects();
    }
    
    this.checkObjects = function() {
        if ( $('html').hasClass('js_active') ) return;
        for ( var i=0; i<vc_anim.objects.length; i++ ) {
            if ( vc_anim.objects[i] !== null && counter.isInViewport( vc_anim.objects[i] ) ) {
                vc_anim.objects[i].addClass("wpb_start_animation animated");
                vc_anim.objects[i] = null;
            }
        }
    }
}

/**
 * Custom trigger events
 * Usually set via button module and appended to templates or content-boxes
 * 
 * deprecated but still used by VC content.
 * moved to Plugin and refactored to 'greyd.trigger'.
 * classes and attributes have changed in Blocks Rendering so the new functions are used instead.
 */
var greyd_trigger = new function() {

    this.init = function() {

        /**
         * Support keys 'Enter' & 'Space' for elements with onclick attribute
         * 
         * still used - deactivate and activate plugin function in one step
         */
        $('[onclick]').on('keydown', function(e) {
            if ( e.keyCode === 13 || e.keyCode === 32 ) {
                e.preventDefault();
                this.click();
            }
        });

        /**
         * Handle hidden links (usually inside content boxes)
         * 
         * deprecated.
         * was only used by blocks, now with new class 'hidden-trigger-link' in plugin
         */
        $('.hidden-box-link').each(function(){
            var elem = this;

            /**
             * Support cmd/ctrl click to open in new tab
             * @param {object} event current JS event
             * @param {object} prevEvent previous JS event, passed via $(...).trigger('click', e);
             */
            $(elem).on('click', function(e, prevEvent) {
                var cmdClicked = e.metaKey || e.ctrlKey;
                var cmdClickedPrev = typeof prevEvent !== 'undefined' && (prevEvent.metaKey || prevEvent.ctrlKey);

                if ( cmdClicked || cmdClickedPrev ) {
                    e.preventDefault();
                    window.open( $(this).attr('href'), '_blank' );
                }
                else if ( typeof prevEvent !== 'undefined' ) {
                    //we actually have to trigger the click because $(...).trigger('click') doesn't
                    this.click();
                }
                
            });

            // When direct siblings are clicked, we trigger the link
            $(elem).siblings(':not([href])').on('click', function(e) {
                $(elem).trigger('click', e);
            });
        });

        /**
         * Setup greyd trigger
         * 
         * handles only old VC trigger
         * Blocks use 'greyd-trigger' data attribute and event name.
         */
        if ( $("[data-ontrigger]").length === 0 ) return;

        $("body").on("greyd_trigger", function( e, event, trigger, is_global ){
            // console.log("Custom event triggered: "+trigger);

            if ( is_global ) {
                var $targets = $("[data-ontrigger*='"+trigger+"|']");
            }
            else {
                var $targets = $( event.target ).closest( ".dynamic, .main, .navigation, body" ).find("[data-ontrigger*='"+trigger+"|']");
            }
            
            if ( $targets.length === 0 ) return;

            $targets.each(function(i, elem){
                var $target = $(elem);
                var regex   = new RegExp( trigger+"[A-Za-z\|]+", "g" );
                var data    = $target.data("ontrigger");
                var setup   = data ? data.match( regex ).slice(-1).pop() : ""; // get the last defined setup for this trigger, as there could be multiple
                var action  = setup ? setup.split("|")[1] : 'show';

                if ( typeof $target[action] === 'function' ) {
                    
                    // hide siblings
                    if ($target.data("siblings") == "hide") $target.siblings("[data-ontrigger]").stop().hide();

                    // call jQuery function
                    $target.stop();
                    $target.css({transition: 'none'});
                    $target[action]();
                }
            });
        });

        this.initBoxStretch();
    }

    this.trigger = function(event, string, is_global) {
        console.log( "Trigger event: "+string+", is global? "+(is_global ? "true" : "false") );

        $("body").trigger("greyd_trigger", [event, string, is_global] );

        // trigger resize event to update nested elements
        window.dispatchEvent(new Event('resize'));
    }


    /**
     * The CSS-class 'stretch-boxes' enables stretching of boxes inside
     * rows to the max height of all boxes before or after a greyd-trigger
     * animation. This is a hidden feature.
     * 
     * @since 1.3.6
     */
    this.initBoxStretch = function() {

        if ( window.innerWidth < 768 || $('.stretch-boxes').length === 0 ) return;

         setTimeout(() => {

            $('.stretch-boxes').each(function(){
                const row = $(this);
                const triggerBoxes = row.find('.vc-content-box[onclick^="greyd_trigger.trigger"], .vc-content-box[onmouseenter^="greyd_trigger.trigger"]');

                let highestHeight = 0;

                // console.log( triggerBoxes );
        
                triggerBoxes.each(function() {
                    const box = $(this);
        
                    const innerTriggerElements = box.find('[data-onload="hide"]');
                    if ( innerTriggerElements.length ) {
        
                        box.css({visibility: 'hidden'});
                        let height = box.height();
        
                        innerTriggerElements.each(function(){
                            // let previousCss  = $(this).attr("style");
        
                            // $(this).css({
                            //     position:   'absolute',
                            //     visibility: 'hidden',
                            //     display:    'block'
                            // });
                            $(this).show();
                            height += $(this).outerHeight();
                            $(this).hide();
        
                            // $(this).attr("style", previousCss ? previousCss : "");
                        })
        
                        box.height( height )
                        box.css({visibility: ''});
                    }
        
                    if ( box.attr('data-ontrigger') && box.data('onload') !== 'hide' ) {
                        const trigger = box.data('ontrigger').split('|')[0];
                        const siblingTriggerElements = box.parent().children('.vc-content-box[data-ontrigger^="'+trigger+'"]');
        
                        siblingTriggerElements.each(function(){
                            let height = $(this).height();
                            if ( !height ) {
                                $(this).show();
                                height = $(this).height();
                                $(this).hide();
                            }
                            // console.log('height', height, this);
                            if ( height > highestHeight ) {
                                highestHeight = height;
                            }
                        });
                    }
                })
        
                triggerBoxes.each(function() {
                    const box = $(this);

                    // console.log( highestHeight, box )
        
                    if ( box.attr('data-ontrigger') && highestHeight > 0 ) {
                        box.height( highestHeight )
                    }
                });
            })
        }, 300)
    }
}