
/**
 * CLASS :: Smart CoreUtils (ES6, Strict Mode)
 *
 * @package Sf.Javascript:Core
 *
 * @desc The class provides the core methods for JavaScript API of Smart.Framework
 * @author unix-world.org
 * @license BSD
 * @file core_utils.js
 * @version 20221016
 * @class smartJ$Utils
 * @static
 * @frozen
 *
 */
const smartJ$Utils = new class {
    constructor() { // STATIC CLASS
        'use strict';
        const _N$ = 'smartJ$Utils';

        // :: static
        const _C$ = this; // self referencing

        const _p$ = console;

        let SECURED = false;
        _C$.secureClass = () => { // implements class security
            if (SECURED === true) {
                _p$.warn(_N$, 'Class is already SECURED');
            } else {
                SECURED = true;
                Object.freeze(_C$);
            } //end if
        }; //END

        const debug = false;


        /**
         * Evaluate Mixed Js Code in a context
         * Will evalute and execute mixed Js Code (Js callable method or Js plain code) within a try/catch context
         * If fails, will log errors to the console and optional if set so it can display an alert message
         *
         * @memberof smartJ$Utils
         * @method evalJsFxCode
         * @static
         * @arrow
         *
         * @param 	{String} 	fxContext 		The Fx context where is executed, used just for logging if fails to know which context is
         * @param 	{JS-Code} 	jsCode 			Js callable method or Js plain code
         * @param 	{Boolean} 	alertErr 		*Optional* Default is FALSE ; If set to TRUE if eval fails will display an alert message
         *
         * @return 	{Boolean} 					TRUE if successful, FALSE otherwise
         */
        const evalJsFxCode = (fxContext, jsCode, alertErr = false) => { // ES6
            // use strict mode in this context ! this is enabled per class, otherwise is mandatory here for security, eval !
            if (jsCode != undefined) {
                const _m$ = 'evalJsFxCode';
                const errMsg = 'Js Code Eval FAILED';
                const errHint = 'See the javascript console for more details ...';
                fxContext = stringPureVal(fxContext);
                if (typeof(jsCode) === 'function') {
                    try {
                        jsCode();
                    } catch (err) {
                        _p$.error(_N$, _m$, 'ERR:', errMsg, 'Context:', fxContext, '[F]', 'JS Errors:', err);
                        if (alertErr === true) {
                            alert('JS Errors: ' + errMsg + ' in [F] Context: ' + fxContext + '\n' + errHint);
                        } //end if
                        return false;
                    }
                } else {
                    jsCode = stringPureVal(jsCode, true);
                    if (stringIStartsWith(jsCode, 'javascript:')) { // eliminate the 'javascript:' prefix, if string may come from an element onSomething html attribute which can contain this as prefix
                        jsCode = jsCode.substr(11);
                        jsCode = stringTrim(jsCode);
                    } //end if
                    if ((typeof(jsCode) === 'string') && (jsCode != 'undefined') && (jsCode != '')) { // important: and if was undefined and if casted by mistake to string by passing from a method to another it may become the 'undefined' string, which is not valid !
                        try {
                            eval('(() => { ' + '\n' + ' \'use strict\';' + '\n' + String(jsCode) + '\n' + ' })();'); // need to be sandboxed in a method ; the code can make use of return ; avoid this type of error !
                        } catch (err) {
                            _p$.error(_N$, _m$, 'ERR:', errMsg, 'Context:', fxContext, '[S]', 'JS Errors:', err);
                            if (alertErr === true) {
                                alert('JS Errors: ' + errMsg + ' in [S] Context: ' + fxContext + '\n' + errHint);
                            } //end if
                            return false;
                        }
                    }
                }
            }
            return true;
        };
        _C$.evalJsFxCode = evalJsFxCode; // export


        /**
         * Check if a number is valid: must be Number, Finite and !NaN
         *
         * @memberof smartJ$Utils
         * @method isFiniteNumber
         * @static
         * @arrow
         *
         * @param 	{Number} 	num 	The number to be tested
         * @return 	{Boolean} 			TRUE is number is Finite and !NaN ; FALSE otherwise
         */
        const isFiniteNumber = (num) => !!(typeof(num) != 'number') ? false : !!(Number.isFinite(num) && (!Number.isNaN(num))); // ES6
        _C$.isFiniteNumber = isFiniteNumber; // export


        /**
         *
         * @memberof smartJ$Utils
         * @method stringPureVal
         * @static
         * @arrow
         *
         * @param 	{Mixed} 	input 	The input
         * @param 	{Boolean} 	trim 	If TRUE will trim the output result
         * @return 	{String} 			The value converted to string, using rules from above
         */
        const stringPureVal = (input, trim) => { // ES6
            //--
            if (
                (typeof(input) == 'undefined') || (input === undefined) ||
                (input === null) || (input === false) ||
                (typeof(input) === 'object') || (typeof(input) === 'function')
            ) {
                input = '';
            } else if (input === true) {
                input = '1';
            } else {
                input = String(input);
            } //end if else
            //--
            if (trim === true) {
                input = stringTrim(input);
            } //end if
            //--
            return String(input);
            //--
        }; // END
        _C$.stringPureVal = stringPureVal; // export


        /**
         * Trim a string (at begining or end by any whitespace: space \ n \ r \ t)
         *
         * @memberof smartJ$Utils
         * @method stringTrim
         * @static
         * @arrow
         *
         * @param 	{String} 	str 	The string to be trimmed
         * @return 	{String} 			The trimmed string
         */
        const stringTrim = (str) => { // ES6
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return '';
            } //end if
            //--
            return String(str.replace(/^\s\s*/, '').replace(/\s\s*$/, '')); // fix to return empty string instead of null
            //--
        }; // END
        _C$.stringTrim = stringTrim; // export


        /**
         * Make uppercase the first character of a string
         * @hint The implementation is compatible with PHP unicode mb_convert_case/MB_CASE_UPPER on the first letter
         *
         * @memberof smartJ$Utils
         * @method stringUcFirst
         * @static
         * @arrow
         *
         * @param 	{String} 	str 	The string to be processed
         * @return 	{String} 			The processed string
         */
        const stringUcFirst = (str) => { // ES6
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return '';
            } //end if
            //--
            if (str.length > 1) {
                return String(str.charAt(0).toUpperCase() + str.substr(1));
            } else {
                return String(str.toUpperCase());
            } //end if else
            //--
        }; //END
        _C$.stringUcFirst = stringUcFirst; // export


        /**
         * Make uppercase the first character of each word in a string while making lowercase the others
         * @hint The implementation is compatible with PHP unicode mb_convert_case/MB_CASE_TITLE
         *
         * @memberof smartJ$Utils
         * @method stringUcWords
         * @static
         * @arrow
         *
         * @param 	{String} 	str 	The string to be processed
         * @return 	{String} 			The processed string
         */
        const stringUcWords = (str) => { // ES6

            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return '';
            } //end if
            //--
            return String(str.toLowerCase().replace(/^(.)|\s+(.)/g, ($1) => $1 ? String($1).toUpperCase() : ''));
            //--
        }; //END
        _C$.stringUcWords = stringUcWords; // export


        /**
         * Replace all occurences in a string - Case Sensitive
         *
         * @memberof smartJ$Utils
         * @method stringReplaceAll
         * @static
         * @arrow
         *
         * @param 	{String} 	token 		The string part to be replaced
         * @param 	{String} 	newToken 	The string part replacement
         * @param 	{String} 	str 		The string where to do the replacements
         * @return 	{String} 				The processed string
         */
        const stringReplaceAll = (token, newToken, str) => { // ES6
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return '';
            } //end if
            //--
            return String(str.split(token).join(newToken)); // fix to return empty string instead of null
            //--
        }; //END
        _C$.stringReplaceAll = stringReplaceAll; // export


        /**
         * Replace all occurences in a string - Case Insensitive
         *
         * @memberof smartJ$Utils
         * @method stringIReplaceAll
         * @static
         *
         * @param 	{String} 	token 		The string part to be replaced
         * @param 	{String} 	newToken 	The string part replacement
         * @param 	{String} 	str 		The string where to do the replacements
         * @return 	{String} 				The processed string
         */
        const stringIReplaceAll = function(token, newToken, str) { // ES6
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return '';
            } //end if
            //--
            if ((typeof(token) === 'string') && (typeof(newToken) === 'string')) {
                //--
                token = token.toLowerCase();
                //--
                let i = -1;
                while ((i = str.toLowerCase().indexOf(token, i >= 0 ? i + newToken.length : 0)) !== -1) {
                    str = String(str.substring(0, i) + newToken + str.substring(i + token.length));
                } //end while
                //--
            } //end if
            //--
            return String(str); // fix to return empty string instead of null
            //--
        }; //END
        _C$.stringIReplaceAll = stringIReplaceAll; // export


        /**
         * Regex Match All occurences.
         * @hint It is compatible just with the PHP preg_match_all()
         *
         * @memberof smartJ$Utils
         * @method stringRegexMatchAll
         * @static
         *
         * @param 	{String} 	str 		The string to be searched
         * @param 	{Regex} 	regexp 		A valid regular expression
         * @return 	{Array} 				The array with matches
         */
        const stringRegexMatchAll = function(str, regexp) { // ES6
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return [];
            } //end if
            //--
            if (regexp == undefined) {
                return [];
            } //end if
            if (!(regexp instanceof RegExp)) {
                _p$.error(_N$, 'ERR: stringRegexMatchAll: 2nd param must be a Regex and is not');
                return [];
            } //end if
            //--
            let matches = [];
            //--
            if (String.prototype.matchAll) { // ES7 version ; FFox67, Chrome73, Safari13, Edge79
                //--
                try { // The RegExp object must have the /g flag, otherwise a TypeError will be thrown.
                    matches = Array.from(str.matchAll(regexp));
                } catch (err) {
                    _p$.error(_N$, 'ERR: stringRegexMatchAll (1):', err);
                    return [];
                } //end try catch
                //--
            } else { // ES6 version
                //--
                const getRegexLimitCycles = () => { // {{{SYNC-JS-SMART-REGEX-RECURSION-LIMIT}}}
                    //--
                    const RegexRecursionLimit = 800000; // set a safe value as for PHP pcre.recursion_limit, but higher enough ; default is 800000
                    //--
                    const min = 100000;
                    const max = min * 10;
                    //--
                    let cycles = RegexRecursionLimit;
                    //--
                    if (cycles < min) {
                        cycles = min;
                    } else if (cycles > max) {
                        cycles = max;
                    } //end if else
                    //--
                    return cycles;
                    //--
                };
                //--
                const maxloops = getRegexLimitCycles();
                //--
                let match = null;
                let loopidx = 0;
                try { // The RegExp object must have the /g flag, otherwise will enter into an infinite loop so a hardlimit of maxloops is used
                    while ((match = regexp.exec(str)) !== null) { // fixed variant
                        if (match.index === regexp.lastIndex) { // avoid infinite loops with zero-width matches
                            regexp.lastIndex++;
                        } //end if
                        matches.push(match);
                        loopidx++;
                        if (loopidx >= maxloops) { // protect against infinite loop
                            _p$.warn(_N$, 'WARN: stringRegexMatchAll hardlimit loop (2). Max recursion depth is', maxloops);
                            break;
                        } //end if
                    } //end while
                } catch (err) {
                    _p$.error(_N$, 'ERR: stringRegexMatchAll (2):', err);
                    return [];
                } //end try catch
                //--
            } //end if else
            //--
            return matches; // Array
            //--
        }; //END
        _C$.stringRegexMatchAll = stringRegexMatchAll; // export


        /*
         * Find if a string contains another sub-string, case sensitive or insensitive
         *
         * @private no export
         *
         * @noexport
         * @static
         * @arrow
         *
         * @param 	{String} 	str 			The string to search in (haystack)
         * @param 	{String} 	search 			The string to search for in haystack (needle)
         * @param 	{Integer} 	pos 			*Optional* ; Default is zero ; The position to start (0 ... n)
         * @param 	{Boolean} 	caseInsensitive *Optional* ; default is FALSE ; If TRUE will do a case insensitive search
         * @return 	{Boolean} 					TRUE if the str contains search begining from the pos
         */
        const strContains = (str, search, pos = 0, caseInsensitive = false) => { // ES6
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return false;
            } //end if
            //--
            search = String((search == undefined) ? '' : search); // force string, test undefined is also for null
            if (search == '') {
                return false;
            } //end if
            //--
            if (pos == undefined) {
                pos = 0;
            } //end if
            pos = format_number_int(pos);
            if (pos < 0) {
                _p$.error(_N$, 'ERR: stringContains: Position is Negative:', pos);
                return false;
            } //end if
            //--
            if (caseInsensitive === true) {
                str = str.toLowerCase();
                search = search.toLowerCase();
            } //end if
            //--
            return !!str.includes(search, pos); // boolean
            //--
        }; //END
        // no export


        /**
         * Find if a string contains another sub-string, case sensitive
         *
         * @memberof smartJ$Utils
         * @method stringContains
         * @static
         * @arrow
         *
         * @param 	{String} 	str 			The string to search in (haystack)
         * @param 	{String} 	search 			The string to search for in haystack (needle)
         * @param 	{Integer} 	pos 			*Optional* ; Default is zero ; The position to start (0 ... n)
         * @return 	{Boolean} 					TRUE if the str contains search begining from the pos
         */
        const stringContains = (str, search, pos = 0) => !!strContains(str, search, pos, false); // ES6
        _C$.stringContains = stringContains; // export


        /**
         * Find if a string contains another sub-string, case insensitive
         *
         * @memberof smartJ$Utils
         * @method stringIContains
         * @static
         * @arrow
         *
         * @param 	{String} 	str 			The string to search in (haystack)
         * @param 	{String} 	search 			The string to search for in haystack (needle)
         * @param 	{Integer} 	pos 			*Optional* ; Default is zero ; The position to start (0 ... n)
         * @return 	{Boolean} 					TRUE if the str contains search begining from the pos
         */
        const stringIContains = (str, search, pos = 0) => !!strContains(str, search, pos, true); // ES6
        _C$.stringIContains = stringIContains; // export


        /*
         * Find if a string starts with another sub-string
         *
         * @private no export
         *
         * @noexport
         * @static
         * @arrow
         *
         * @param 	{String} 	str 			The string to search in (haystack)
         * @param 	{String} 	search 			The string to search for in haystack (needle)
         * @param 	{Boolean} 	caseInsensitive *Optional* ; default is FALSE ; If TRUE will do a case insensitive search
         * @return 	{Boolean} 					TRUE if the str starts with the search
         */
        const strStartsWith = (str, search, caseInsensitive = false) => { // ES6
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return false;
            } //end if
            //--
            search = String((search == undefined) ? '' : search); // force string, test undefined is also for null
            if (search == '') {
                return false;
            } //end if
            //--
            if (caseInsensitive === true) {
                str = str.toLowerCase();
                search = search.toLowerCase();
            } //end if
            //--
            return !!str.startsWith(search, 0); // bool
            //--
        }; //END
        // no export


        /**
         * Find if a string starts with another sub-string, case sensitive
         *
         * @memberof smartJ$Utils
         * @method stringStartsWith
         * @static
         * @arrow
         *
         * @param 	{String} 	str 			The string to search in (haystack)
         * @param 	{String} 	search 			The string to search for in haystack (needle)
         * @return 	{Boolean} 					TRUE if the str starts with the search
         */
        const stringStartsWith = (str, search) => !!strStartsWith(str, search, false); // ES6
        _C$.stringStartsWith = stringStartsWith; // export


        /**
         * Find if a string starts with another sub-string, case insensitive
         *
         * @memberof smartJ$Utils
         * @method stringIStartsWith
         * @static
         * @arrow
         *
         * @param 	{String} 	str 			The string to search in (haystack)
         * @param 	{String} 	search 			The string to search for in haystack (needle)
         * @return 	{Boolean} 					TRUE if the str starts with the search
         */
        const stringIStartsWith = (str, search) => !!strStartsWith(str, search, true); // ES6
        _C$.stringIStartsWith = stringIStartsWith; // export


        /*
         * Find if a string ends with another sub-string
         *
         * @private no export
         *
         * @noexport
         * @static
         * @arrow
         *
         * @param 	{String} 	str 			The string to search in (haystack)
         * @param 	{String} 	search 			The string to search for in haystack (needle)
         * @param 	{Boolean} 	caseInsensitive *Optional* ; default is FALSE ; If TRUE will do a case insensitive search
         * @return 	{Boolean} 					TRUE if the str ends with the search
         */
        const strEndsWith = (str, search, caseInsensitive = false) => { // ES6
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return false;
            } //end if
            //--
            search = String((search == undefined) ? '' : search); // force string, test undefined is also for null
            if (search == '') {
                return false;
            } //end if
            //--
            if (caseInsensitive === true) {
                str = str.toLowerCase();
                search = search.toLowerCase();
            } //end if
            //--
            return !!str.endsWith(search); // bool
            //--
        }; //END
        // no export


        /**
         * Find if a string ends with another sub-string, case sensitive
         *
         * @memberof smartJ$Utils
         * @method stringEndsWith
         * @static
         * @arrow
         *
         * @param 	{String} 	str 			The string to search in (haystack)
         * @param 	{String} 	search 			The string to search for in haystack (needle)
         * @return 	{Boolean} 					TRUE if the str ends with the search
         */
        const stringEndsWith = (str, search) => !!strEndsWith(str, search, false); // ES6
        _C$.stringEndsWith = stringEndsWith; // export


        /**
         * Find if a string ends with another sub-string, case insensitive
         *
         * @memberof smartJ$Utils
         * @method stringIEndsWith
         * @static
         * @arrow
         *
         * @param 	{String} 	str 			The string to search in (haystack)
         * @param 	{String} 	search 			The string to search for in haystack (needle)
         * @return 	{Boolean} 					TRUE if the str ends with the search
         */
        const stringIEndsWith = (str, search) => !!strEndsWith(str, search, true); // ES6
        _C$.stringIEndsWith = stringIEndsWith; // export


        /**
         * Quote regular expression characters in a string.
         * @hint It is compatible with PHP preg_quote() method.
         *
         * @memberof smartJ$Utils
         * @method preg_quote
         * @static
         * @arrow
         *
         * @param 	{String} 	str 		The string to be processed
         * @return 	{String} 				The processed string
         */
        const preg_quote = (str) => { // ES6
            //--
            str = String((str == undefined) ? '' : str);
            if (str == '') {
                return '';
            } //end if
            //--
            // http://kevin.vanzonneveld.net
            // + original by: booeyOH
            // + improved by: Ates Goral (http://magnetiq.com)
            // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
            // + bugfixed by: Onno Marsman
            // *   example 1: preg_quote("$40");
            // *   returns 1: '\$40'
            // *   example 2: preg_quote("*RRRING* Hello?");
            // *   returns 2: '\*RRRING\* Hello\?'
            // *   example 3: preg_quote("\\.+*?[^]$(){}=!<>|:");
            // *   returns 3: '\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:'
            // improved by: unix-world.org
            //--
            return String(str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:\-\#])/g, '\\$1'));
            //--
        }; //END
        _C$.preg_quote = preg_quote; // export


        /*
         * Split string by character with trimming prefix/suffix
         *
         * @private no export
         *
         * @noexport
         * @static
         *
         * @param 	{Enum} 		str 	The string to be splitted by 2nd param
         * @param 	{String} 	by 		The character the string to be splitted on ; available characters: '=' | ':' | ';' | ','
         * @return 	{Array} 			The array with string parts splitted
         */
        const stringSplitbyChar = function(str, by) { // ES6
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return []; // empty string
            } //end if
            //--
            by = String((by == undefined) ? '' : by); // force string, test undefined is also for null
            let regex = false;
            switch (by) {
                case '=':
                    regex = true; // /\s*\=\s*/;
                    break;
                case ':':
                    regex = true; // /\s*\:\s*/;
                    break;
                case ';':
                    regex = true; // /\s*;\s*/;
                    break;
                case ',':
                    regex = true; // /\s*,\s*/;
                    break;
                default:
                    regex = false; // invalid
            } //end switch
            //--
            if (!regex) {
                return []; // invalid
            } //end if
            //--
            regex = new RegExp('\\s*' + preg_quote(by) + '\\s*');
            //--
            str = stringTrim(str);
            //--
            return str.split(regex); // Array
            //--
        }; //END
        // no export


        /**
         * Split string by equal (=) with trimming prefix/suffix
         *
         * @memberof smartJ$Utils
         * @method stringSplitbyEqual
         * @static
         * @arrow
         *
         * @param 	{String} 	str 	The string to be splitted by = (equal)
         * @return 	{Array} 			The array with string parts splitted
         */
        const stringSplitbyEqual = (str) => stringSplitbyChar(str, '='); // Array, ES6
        _C$.stringSplitbyEqual = stringSplitbyEqual; // export


        /**
         * Split string by colon (:) with trimming prefix/suffix
         *
         * @memberof smartJ$Utils
         * @method stringSplitbyColon
         * @static
         * @arrow
         *
         * @param 	{String} 	str 	The string to be splitted by : (colon)
         * @return 	{Array} 			The array with string parts splitted
         */
        const stringSplitbyColon = (str) => stringSplitbyChar(str, ':'); // Array, ES6
        _C$.stringSplitbyColon = stringSplitbyColon; // export


        /**
         * Split string by semicolon (;) with trimming prefix/suffix
         *
         * @memberof smartJ$Utils
         * @method stringSplitbySemicolon
         * @static
         * @arrow
         *
         * @param 	{String} 	str 		The string to be splitted by ; (semicolon)
         * @return 	{Array} 				The array with string parts splitted
         */
        const stringSplitbySemicolon = (str) => stringSplitbyChar(str, ';'); // Array, ES6
        _C$.stringSplitbySemicolon = stringSplitbySemicolon; // export


        /**
         * Split string by comma (,) with trimming prefix/suffix
         *
         * @memberof smartJ$Utils
         * @method stringSplitbyComma
         * @static
         * @arrow
         *
         * @param 	{String} 	str 	The string to be splitted by , (comma)
         * @return 	{Array} 			The array with string parts splitted
         */
        const stringSplitbyComma = (str) => stringSplitbyChar(str, ','); // Array, ES6
        _C$.stringSplitbyComma = stringSplitbyComma; // export


        /**
         * Get the first element from an Array
         *
         * @memberof smartJ$Utils
         * @method arrayGetFirst
         * @static
         * @arrow
         *
         * @param 	{Array} 	arr 		The array to be used
         * @return 	{Mixed} 				The first element from the array
         */
        const arrayGetFirst = (arr) => (arr instanceof Array) ? arr.shift() : null; // Mixed, ES6
        _C$.arrayGetFirst = arrayGetFirst; // export


        /**
         * Get the last element from an Array
         *
         * @memberof smartJ$Utils
         * @method arrayGetLast
         * @static
         * @arrow
         *
         * @param 	{Array} 	arr 		The array to be used
         * @return 	{Mixed} 				The last element from the array
         */
        const arrayGetLast = (arr) => (arr instanceof Array) ? arr.pop() : null; // Mixed, ES6
        _C$.arrayGetLast = arrayGetLast; // export


        /**
         * Format a number as FLOAT
         *
         * @memberof smartJ$Utils
         * @method format_number_float
         * @static
         * @arrow
         *
         * @param 	{Numeric} 	num 					A numeric value
         * @param 	{Boolean} 	allow_negatives 		*Optional* ; default is TRUE ; If set to FALSE will disallow negative values and if negative value detected will reset it to zero
         * @return 	{Float} 							A float number
         */
        const format_number_float = (num, allow_negatives = true) => { // ES6
            //--
            if (num == undefined) {
                num = 0;
            } //end if
            num = Number(num);
            num = isFiniteNumber(num) ? num : 0;
            if (allow_negatives !== true) {
                if (num < 0) {
                    num = 0;
                } //end if
            } //end if
            num = Number(num);
            //--
            return isFiniteNumber(num) ? num : 0; // Float
            //--
        }; //END
        _C$.format_number_float = format_number_float; // export


        /**
         * Format a number as INTEGER
         *
         * @memberof smartJ$Utils
         * @method format_number_int
         * @static
         * @arrow
         *
         * @param 	{Numeric} 	y_number 				A numeric value
         * @param 	{Boolean} 	y_allow_negatives 		If TRUE will allow negative values else will return just positive (unsigned) values
         * @return 	{Integer} 							An integer number
         */
        const format_number_int = (num, allow_negatives = true) => { // ES6
            //--
            if (num == undefined) {
                num = 0;
            } //end if
            num = Number(num);
            num = isFiniteNumber(num) ? Math.round(num) : 0;
            if (!Number.isInteger(num)) {
                num = 0; // check
            } //end if
            if (allow_negatives !== true) {
                if (num < 0) {
                    num = 0;
                } //end if
            } //end if
            if (!Number.isSafeInteger(num)) { // {{{SMART-JS-NEWEST-METHOD}}}
                if (num > 0) {
                    num = Number.MAX_SAFE_INTEGER;
                } else if (num < 0) {
                    num = Number.MIN_SAFE_INTEGER;
                } //end if else
            } //end if
            num = Number(num);
            //--
            return (isFiniteNumber(num) && Number.isInteger(num)) ? num : 0; // Integer
            //--
        }; //END
        _C$.format_number_int = format_number_int; // export


        /**
         * Format a number as DECIMAL
         *
         * @memberof smartJ$Utils
         * @method format_number_dec
         * @static
         * @arrow
         *
         * @param 	{Numeric} 	y_number 					A numeric value as Number or String
         * @param 	{Integer} 	y_decimals 					*Optional* Default is 2 ; The number of decimal to use (between 1 and 13)
         * @param 	{Boolean} 	y_allow_negatives 			*Optional* Default is TRUE ; If FALSE will disallow negative (will return just positive / unsigned values)
         * @param 	{Boolean} 	y_discard_trailing_zeroes 	*Optional* Default is FALSE ; If set to TRUE will discard trailing zeroes
         * @return 	{String} 								A decimal number as string to keep the fixed decimals as specified
         */
        const format_number_dec = (num, decimals = 2, allow_negatives = true, discard_trailing_zeroes = false) => { // ES6
            //--
            if (num == undefined) {
                num = 0;
            } //end if
            //--
            if (decimals == undefined) {
                decimals = 2; // default
            } //end if
            decimals = format_number_int(decimals, false);
            if (decimals < 1) {
                decimals = 1;
            } else if (decimals > 13) {
                decimals = 13;
            } //end if else
            //--
            if (allow_negatives !== false) {
                allow_negatives = true; // default
            } //end if
            //--
            if (discard_trailing_zeroes !== true) {
                discard_trailing_zeroes = false; // default
            } //end if
            //--
            num = format_number_float(num, allow_negatives);
            //--
            if (allow_negatives !== true) {
                if (num < 0) {
                    num = 0;
                } //end if
            } //end if
            //--
            num = num.toFixed(decimals);
            if (discard_trailing_zeroes !== false) {
                num = Number.parseFloat(num); // must be parse float here
            } //end if
            //--
            return String(num); // String
            //--
        }; //END
        _C$.format_number_dec = format_number_dec; // export


        /**
         * Un-quotes a quoted string.
         * It will remove double / quoted slashes.
         * @hint It is compatible with PHP stripslashes() method.
         *
         * @memberof smartJ$Utils
         * @method stripslashes
         * @static
         *
         * @param {String} str The string to be processed
         * @return {String} The processed string
         */
        const stripslashes = function(str) {
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return '';
            } //end if
            //--
            const replacer = (s, n1) => {
                switch (n1) {
                    case '\\':
                        return '\\';
                    case '0':
                        return '\u0000';
                    case '':
                        return '';
                    default:
                        return String(n1);
                } //end switch
            };
            //-- original written by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) ; improved by: Ates Goral, marrtins, rezna ; fixed / bugfixed by: Mick@el, Onno Marsman, Brett Zamir, Rick Waldron, Brant Messenger
            return String(str.replace(/\\(.?)/g, replacer));
            //--
        }; //END
        _C$.stripslashes = stripslashes; // export


        /**
         * Quote string with slashes in a C style.
         * @hint It is compatible with PHP addcslashes() method.
         *
         * @memberof smartJ$Utils
         * @method addcslashes
         * @static
         *
         * @param 	{String} 	str 		The string to be escaped
         * @param 	{String} 	charlist 	A list of characters to be escaped. If charlist contains characters \n, \r etc., they are converted in C-like style, while other non-alphanumeric characters with ASCII codes lower than 32 and higher than 126 converted to octal representation
         * @return 	{String} 				Returns the escaped string
         */
        const addcslashes = function(str, charlist) { // ES6
            //--
            //  discuss at: http://phpjs.org/functions/addcslashes/
            // original by: Brett Zamir (http://brett-zamir.me)
            //        note: We show double backslashes in the return value example code below because a JavaScript string will not
            //        note: render them as backslashes otherwise
            //   example 1: addcslashes('foo[ ]', 'A..z'); // Escape all ASCII within capital A to lower z range, including square brackets
            //   returns 1: "\\f\\o\\o\\[ \\]"
            //   example 2: addcslashes("zoo['.']", 'z..A'); // Only escape z, period, and A here since not a lower-to-higher range
            //   returns 2: "\\zoo['\\.']"
            //   example 3: addcslashes("@a\u0000\u0010\u00A9", "\x00..\x1F!@\x7F..\xFF"); // Escape as octals those specified and less than 32 (0x20) or greater than 126 (0x7E), but not otherwise
            //   returns 3: '\\@a\\000\\020\\302\\251'
            //   example 4: addcslashes("\u0020\u007E", "\x20..\x7D"); // Those between 32 (0x20 or 040) and 126 (0x7E or 0176) decimal value will be backslashed if specified (not octalized)
            //   returns 4: '\\ ~'
            //   example 5: addcslashes("\r\u0007\n", '\x00..\x1F'); // Recognize C escape sequences if specified
            //   returns 5: "\\r\\a\\n"
            //   example 6: addcslashes("\r\u0007\n", '\x00'); // Do not recognize C escape sequences if not specified
            //   returns 6: "\r\u0007\n"
            // improved by: unix-world.org
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return '';
            } //end if
            //--
            charlist = String((charlist == undefined) ? '' : charlist); // force string, test undefined is also for null
            if (charlist == '') {
                return '';
            } //end if
            //--
            let target = '',
                chrs = [],
                i = 0,
                j = 0,
                c = '',
                next = '',
                rangeBegin = '',
                rangeEnd = '',
                chr = '',
                begin = 0,
                end = 0,
                octalLength = 0,
                postOctalPos = 0,
                cca = 0,
                escHexGrp = [],
                encoded = '';
            //--
            const percentHex = /%([\dA-Fa-f]+)/g;
            //--
            const _pad = (n, c) => {
                if ((n = String(n)).length < c) {
                    return new Array(++c - n.length).join('0') + n;
                } //end if
                return n;
            };
            //--
            for (i = 0; i < charlist.length; i++) {
                c = charlist.charAt(i);
                next = charlist.charAt(i + 1);
                if (c === '\\' && next && (/\d/).test(next)) {
                    rangeBegin = charlist.slice(i + 1).match(/^\d+/)[0]; // Octal
                    octalLength = rangeBegin.length;
                    postOctalPos = i + octalLength + 1;
                    if (charlist.charAt(postOctalPos) + charlist.charAt(postOctalPos + 1) === '..') {
                        begin = rangeBegin.charCodeAt(0); // Octal begins range
                        if ((/\\\d/).test(charlist.charAt(postOctalPos + 2) + charlist.charAt(postOctalPos + 3))) {
                            rangeEnd = charlist.slice(postOctalPos + 3).match(/^\d+/)[0]; // Range ends with octal
                            i += 1; // Skip range end backslash
                        } else if (charlist.charAt(postOctalPos + 2)) {
                            rangeEnd = charlist.charAt(postOctalPos + 2); // Range ends with character
                        } else {
                            _p$.error(_N$, 'ERR: addcslashes: Range with no end point (1)');
                        } //end if else
                        end = rangeEnd.charCodeAt(0);
                        if (end > begin) { // Treat as a range
                            for (j = begin; j <= end; j++) {
                                chrs.push(String.fromCharCode(j));
                            } //end for
                        } else { // Supposed to treat period, begin and end as individual characters only, not a range
                            chrs.push('.', rangeBegin, rangeEnd);
                        } //end if else
                        i += rangeEnd.length + 2; // Skip dots and range end (already skipped range end backslash if present)
                    } else { // Octal is by itself
                        chr = String.fromCharCode(parseInt(rangeBegin, 8)); // must be parse int here
                        chrs.push(chr);
                    } //end if else
                    i += octalLength; // Skip range begin
                } else if (next + charlist.charAt(i + 2) === '..') { // Character begins range
                    rangeBegin = c;
                    begin = rangeBegin.charCodeAt(0);
                    if ((/\\\d/).test(charlist.charAt(i + 3) + charlist.charAt(i + 4))) { // Range ends with octal
                        rangeEnd = charlist.slice(i + 4).match(/^\d+/)[0];
                        i += 1; // Skip range end backslash
                    } else if (charlist.charAt(i + 3)) {
                        rangeEnd = charlist.charAt(i + 3); // Range ends with character
                    } else {
                        _p$.error(_N$, 'ERR: addcslashes: Range with no end point (2)');
                    } //end if else
                    end = rangeEnd.charCodeAt(0);
                    if (end > begin) { // Treat as a range
                        for (j = begin; j <= end; j++) {
                            chrs.push(String.fromCharCode(j));
                        } //end for
                    } else {
                        chrs.push('.', rangeBegin, rangeEnd); // Supposed to treat period, begin and end as individual characters only, not a range
                    } //end if else
                    i += rangeEnd.length + 2; // Skip dots and range end (already skipped range end backslash if present)
                } else { // Character is by itself
                    chrs.push(c);
                } //end if else
            } //end for
            //--
            for (i = 0; i < str.length; i++) {
                c = str.charAt(i);
                if (chrs.indexOf(c) !== -1) {
                    target += '\\';
                    cca = c.charCodeAt(0);
                    if (cca < 32 || cca > 126) { // Needs special escaping
                        switch (c) {
                            case '\n':
                                target += 'n';
                                break;
                            case '\t':
                                target += 't';
                                break;
                            case '\u000D':
                                target += 'r';
                                break;
                            case '\u0007':
                                target += 'a';
                                break;
                            case '\v':
                                target += 'v';
                                break;
                            case '\b':
                                target += 'b';
                                break;
                            case '\f':
                                target += 'f';
                                break;
                            default:
                                //target += _pad(cca.toString(8), 3);break; // it is Sufficient only for UTF-16 ; below handles all
                                encoded = encodeURIComponent(c);
                                let escHexGrps = stringRegexMatchAll(encoded, percentHex); // Array
                                let z;
                                for (z = 0; z < escHexGrps.length; z++) { // 3-length-padded UTF-8 octets
                                    if (z > 0) { // already added a slash above, so add only for cycles >= 1
                                        target += '\\';
                                    } //end if
                                    target += _pad(parseInt(escHexGrps[z][1], 16).toString(8), 3); // must be parse int here
                                } //end for
                                break;
                        } //end switch
                    } else { // Perform regular backslashed escaping
                        target += c;
                    } //end if else
                } else { // Just add the character unescaped
                    target += c;
                } //end if else
            } //end for
            //--
            return String(target ? target : '');
            //--
        }; //END
        _C$.addcslashes = addcslashes; // export


        /**
         * Convert special characters to HTML entities.
         * @hint It is like the Smart::escape_css() from the PHP Smart.Framework.
         *
         * @memberof smartJ$Utils
         * @method escape_css
         * @static
         * @arrow
         *
         * @param 	{String} 	str 		The string to be escaped
         * @return 	{String} 				The safe escaped string to be injected in CSS code
         */
        const escape_css = (str) => { // ES6
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return '';
            } //end if
            //--
            return String(addcslashes(String((str == undefined) ? '' : str), "\x00..\x1F!\"#$%&'()*+,./:;<=>?@[\\]^`{|}~"));
            //--
        }; //END
        _C$.escape_css = escape_css; // export


        /*
         * Convert special characters to HTML entities with Options.
         * Depending on the flag parameter, the following values will be converted to safe HTML entities:
         * 		ENT_COMPAT: 	< > & "
         * 		ENT_QUOTES: 	< > & " '
         * 		ENT_NOQUOTES: 	< > &
         * @hint It is like the htmlspecialchars() from PHP.
         *
         * @private internal development only
         *
         * @memberof smartJ$Utils
         * @method htmlspecialchars
         * @static
         * @arrow
         *
         * @param 	{String} 	str 		The string to be escaped
         * @param 	{Enum} 		flag 		*Optional* A bitmask of one or more of the following flags: ENT_COMPAT (default) ; ENT_QUOTES ; ENT_NOQUOTES
         * @return 	{String} 				The safe escaped string to be injected in HTML code
         */
        const htmlspecialchars = (str, flag = 'ENT_COMPAT') => { // ES6
            //-- format string
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return '';
            } //end if
            //-- test empty flag
            flag = String((flag == undefined) ? '' : flag); // force string, test undefined is also for null
            if (flag == '') {
                flag = 'ENT_COMPAT';
            } //end if
            //-- replace basics
            str = str.replace(/&/g, '&amp;');
            str = str.replace(/\</g, '&lt;');
            str = str.replace(/\>/g, '&gt;');
            //-- replace quotes, depending on flag
            if (flag == 'ENT_QUOTES') { // ENT_QUOTES
                //-- replace all quotes: ENT_QUOTES
                str = str.replace(/"/g, '&quot;');
                str = str.replace(/'/g, '&#039;');
                //--
            } else if (flag != 'ENT_NOQUOTES') { // ENT_COMPAT
                //-- default, replace just double quotes
                str = str.replace(/"/g, '&quot;');
                //--
            } //end if else
            //--
            return String(str); // fix to return empty string instead of null
            //--
        }; //END
        _C$.htmlspecialchars = htmlspecialchars; // export, hidden


        /**
         * Convert special characters to HTML entities.
         * These values will be converted to safe HTML entities: < > & "
         * @hint It is like the Smart::escape_html() from the PHP Smart.Framework.
         *
         * @memberof smartJ$Utils
         * @method escape_html
         * @static
         * @arrow
         *
         * @param 	{String} 	str 		The string to be escaped
         * @return 	{String} 				The safe escaped string to be injected in HTML code
         */
        const escape_html = (str) => String(htmlspecialchars(str, 'ENT_COMPAT')); // ES6
        _C$.escape_html = escape_html; // export


        /**
         * Convert special characters to escaped entities for safe use with Javascript Strings.
         * @hint It is like the Smart::escape_js() from the PHP Smart.Framework.
         *
         * @memberof smartJ$Utils
         * @method escape_js
         * @static
         *
         * @param 	{String} 	str 		The string to be escaped
         * @return 	{String} 				The escaped string using the json encode standard to be injected between single quotes '' or double quotes ""
         */
        const escape_js = function(str) { // (ES6)
            //--
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return '';
            } //end if
            //-- escape a string as unicode
            const escape_unicode = (str) => {
                str = String((str == undefined) ? '' : str);
                if (str == '') {
                    return '';
                } //end if
                return String('\\u' + ('0000' + str.charCodeAt(0).toString(16)).slice(-4).toLowerCase());
            };
            //-- table of character substitutions: get from json2.js but excludding the " which is done later to preserve compatibility with PHP
            const replace_meta = (str) => {
                //--
                str = String((str == undefined) ? '' : str);
                if (str == '') {
                    return '';
                } //end if
                //--
                switch (str) {
                    case '\b':
                        str = '\\b';
                        break;
                    case '\t':
                        str = '\\t';
                        break;
                    case '\n':
                        str = '\\n';
                        break;
                    case '\f':
                        str = '\\f';
                        break;
                    case '\r':
                        str = '\\r';
                        break;
                    case '\\':
                        str = '\\\\';
                        break;
                    default:
                        str = escape_unicode(str);
                } //end switch
                return String(str);
            };
            //-- init
            let encoded = '';
            //-- replace meta
            encoded = str.replace(/[\x00-\x1f\x7f-\x9f\\]/g, (a) => replace_meta(a));
            //-- replace unicode characters
            encoded = encoded.replace(/[\u007F-\uFFFF]/g, (c) => escape_unicode(c));
            //-- replace special characters (use uppercase unicode escapes as in PHP ; example: u003C / u003E )
            encoded = encoded.replace(/[\u0026]/g, '\\u0026'); // & 	JSON_HEX_AMP
            encoded = encoded.replace(/[\u0022]/g, '\\u0022'); // " 	JSON_HEX_QUOT
            encoded = encoded.replace(/[\u0027]/g, '\\u0027'); // ' 	JSON_HEX_APOS
            encoded = encoded.replace(/[\u003C]/g, '\\u003C'); // < 	JSON_HEX_TAG
            encoded = encoded.replace(/[\u003E]/g, '\\u003E'); // > 	JSON_HEX_TAG
            encoded = encoded.replace(/[\/]/g, '\\/'); // / 	JSON_UNESCAPED_SLASHES
            //-- return string
            return String(encoded); // fix to return empty string instead of null
            //--
        }; //END
        _C$.escape_js = escape_js; // export


        /**
         * Safe escape URL Variable (using RFC3986 standards to be full Unicode compliant).
         * @hint It is like the Smart::escape_url() ; can be used as a shortcut to the encodeURIComponent() to provide a standard into Smart.Framework/JS.
         *
         * @memberof smartJ$Utils
         * @method escape_url
         * @static
         * @arrow
         *
         * @param 	{String} 	str 		The URL variable value to be escaped
         * @return 	{String} 				The escaped URL variable
         */
        const escape_url = (str) => { // ES6
            //-- format string
            str = String((str == undefined) ? '' : str); // force string, test undefined is also for null
            if (str == '') {
                return '';
            } //end if
            //--
            str = String(encodeURIComponent(str));
            //-- fixes to make it more compliant with it RFC 3986
            str = str.replace('!', '%21');
            str = str.replace("'", '%27');
            str = str.replace('(', '%28');
            str = str.replace(')', '%29');
            str = str.replace('*', '%2A');
            //--
            return String(str); // fix to return empty string instead of null
            //--
        }; //END
        _C$.escape_url = escape_url; // export


        /**
         * Replace new lines \ r \ n ; \ n with the <br> html tag.
         * @hint It is compatible with the PHP nl2br() method.
         *
         * @memberof smartJ$Utils
         * @method nl2br
         * @static
         * @arrow
         *
         * @param {String} str The string to be processed
         * @return {String} The processed string with <br> html tags if new lines were detected
         */
        const nl2br = (str) => { // ES6
            //--
            str = String((str == undefined) ? '' : str);
            if (str == '') {
                return '';
            } //end if
            //--
            return String(str.replace(/\r\n/g, /\n/).replace(/\r/g, /\n/).replace(/\n/g, '<br>')); // fix to return empty string instead of null
            //--
        }; //END
        _C$.nl2br = nl2br; // export


        /**
         * Encodes an ISO-8859-1 string to UTF-8
         *
         * @memberof smartJ$Utils
         * @method utf8_encode
         * @static
         *
         * @param 	{String} 	str 			The string to be processed
         * @return 	{String} 					The processed string
         */
        const utf8_encode = function(str) { // ES6
            //--
            str = String((str == undefined) ? '' : str);
            if (str == '') {
                return '';
            } //end if
            //--
            let utftext = '';
            //--
            //	str = str.replace(/\r\n/g, '\n');
            str = str.replace(/\r\n/g, /\n/).replace(/\r/g, /\n/); // fix, replace both: \r\n and \r to \n
            for (let n = 0; n < str.length; n++) {
                let c = str.charCodeAt(n);
                if (c < 128) {
                    utftext += String.fromCharCode(c);
                } else if ((c > 127) && (c < 2048)) {
                    utftext += String.fromCharCode((c >> 6) | 192);
                    utftext += String.fromCharCode((c & 63) | 128);
                } else {
                    utftext += String.fromCharCode((c >> 12) | 224);
                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                    utftext += String.fromCharCode((c & 63) | 128);
                } //end if else
            } //end for
            //--
            return String(utftext); // fix to return empty string instead of null
            //--
        }; //END
        _C$.utf8_encode = utf8_encode; // export


        /**
         * Decodes an UTF-8 string to ISO-8859-1
         *
         * @memberof smartJ$Utils
         * @method utf8_decode
         * @static
         *
         * @param 	{String} 	utftext 		The string to be processed
         * @return 	{String} 					The processed string
         */
        const utf8_decode = function(utftext) { // ES6
            //--
            utftext = String((utftext == undefined) ? '' : utftext);
            if (utftext == '') {
                return '';
            } //end if
            //--
            let str = '';
            //--
            let i = 0;
            let c, c1, c2, c3;
            c = c1 = c2 = c3 = 0;
            while (i < utftext.length) {
                c = utftext.charCodeAt(i);
                if (c < 128) {
                    str += String.fromCharCode(c);
                    i++;
                } else if ((c > 191) && (c < 224)) {
                    c2 = utftext.charCodeAt(i + 1);
                    str += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                    i += 2;
                } else {
                    c2 = utftext.charCodeAt(i + 1);
                    c3 = utftext.charCodeAt(i + 2);
                    str += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                    i += 3;
                } //end if else
            } //end while
            //--
            return String(str); // fix to return empty string instead of null
            //--
        }; //END
        _C$.utf8_decode = utf8_decode; // export


        /**
         * De-Accent a latin-based Unicode string
         * Will convert all accented characters in UTF-8 / ISO-8859-* with their unnaccented versions into ISO-8859-1
         * @hint It is like PHP SmartUnicode deaccent str
         *
         * @memberof smartJ$Utils
         * @method deaccent_str
         * @static
         *
         * @param 	{String} 	strIn 		The string to be de-accented
         * @return 	{String} 				The de-accented string
         */
        const deaccent_str = function(strIn) { // ES6
            //--
            strIn = String((strIn == undefined) ? '' : strIn);
            if (strIn == '') {
                return '';
            } //end if
            //-- deaccent strings c.176x2 (v.170305), pgsql
            const data_accented = 'áâãäåāăąÁÂÃÄÅĀĂĄćĉčçĆĈČÇďĎèéêëēĕėěęÈÉÊËĒĔĖĚĘĝģĜĢĥħĤĦìíîïĩīĭȉȋįÌÍÎÏĨĪĬȈȊĮĳĵĲĴķĶĺļľłĹĻĽŁñńņňÑŃŅŇóôõöōŏőøœÒÓÔÕÖŌŎŐØŒŕŗřŔŖŘșşšśŝšȘŞŠŚŜŠțţťȚŢŤùúûüũūŭůűųÙÚÛÜŨŪŬŮŰŲŵŴẏỳŷÿýẎỲŶŸÝźżžŹŻŽ';
            const data_deaccented = 'aaaaaaaaAAAAAAAAccccCCCCdDeeeeeeeeeEEEEEEEEEggGGhhHHiiiiiiiiiiIIIIIIIIIIjjJJkKllllLLLLnnnnNNNNoooooooooOOOOOOOOOOrrrRRRssssssSSSSSStttTTTuuuuuuuuuuUUUUUUUUUUwWyyyyyYYYYYzzzZZZ';
            //--
            strIn = strIn.split('');
            let len = strIn.length;
            let strOut = new Array();
            //--
            let theIdxFound = -1;
            for (let y = 0; y < len; y++) {
                theIdxFound = data_accented.indexOf(strIn[y]);
                if (theIdxFound !== -1) {
                    strOut[y] = data_deaccented.substr(theIdxFound, 1);
                } else {
                    strOut[y] = strIn[y];
                } //end if else
            } //end for
            //--
            strOut = strOut.join('');
            //--
            return String(strOut);
            //--
        }; //END
        _C$.deaccent_str = deaccent_str; // export


        /**
         * Convert binary data into hexadecimal representation.
         * @hint It is compatible with PHP bin2hex() method.
         *
         * @memberof smartJ$Utils
         * @method bin2hex
         * @static
         *
         * @param 	{String} 	s 			The string to be processed
         * @param 	{Boolean} 	bin 		Set to TRUE if the string is binary to avoid re-encode to UTF-8
         * @return 	{String} 				The processed string
         */
        const bin2hex = function(s, bin = false) { // ES6
            //--
            s = String((s == undefined) ? '' : s);
            if (s == '') {
                return '';
            } //end if
            //--
            if (bin !== true) { // binary content must not be re-encoded to UTF-8
                s = String(utf8_encode(s)); // force string and make it unicode safe
            } //end if
            //--
            let hex = '';
            let i, l, n;
            for (i = 0, l = s.length; i < l; i++) {
                n = s.charCodeAt(i).toString(16);
                hex += n.length < 2 ? '0' + n : n;
            } //end for
            //--
            return String(hex).toLowerCase(); // fix to return empty string instead of null
            //--
        }; //END
        _C$.bin2hex = bin2hex; // export


        /**
         * Decodes a hexadecimally encoded binary string.
         * @hint It is compatible with PHP hex2bin() method.
         *
         * @memberof smartJ$Utils
         * @method hex2bin
         * @static
         *
         * @param 	{String} 	hex 		The string to be processed
         * @param 	{Boolean} 	bin 		Set to TRUE if the string is binary to avoid re-decode as UTF-8
         * @return 	{String} 				The processed string
         */
        const hex2bin = function(hex, bin = false) { // ES6
            //--
            hex = String((hex == undefined) ? '' : hex);
            hex = String(stringTrim(hex)).toLowerCase(); // force string and trim to avoid surprises ...
            if (hex == '') {
                return '';
            } //end if
            //--
            let bytes = [],
                str;
            //--
            for (let i = 0; i < hex.length - 1; i += 2) {
                bytes.push(parseInt(hex.substr(i, 2), 16)); // must be parse int here
            } //end for
            //-- fix to return empty string instead of null
            if (bin !== true) { // binary content must not be re-decoded as UTF-8
                return String(utf8_decode(String.fromCharCode.apply(String, bytes)));
            } else {
                return String.fromCharCode.apply(String, bytes);
            } //end if else
            //--
        }; //END
        _C$.hex2bin = hex2bin; // export


        /**
         * Generate a base36 10-chars length UUID
         *
         * @memberof smartJ$Utils
         * @method uuid
         * @static
         *
         * @return 	{String} 			A unique UUID, time based, base36 ; Ex: 1A2B3C4D5E
         */
        let tseed = (new Date()).valueOf(); // time based uuid seed, must be global in the class as it is re-seeded each time UUID is generated
        const uuid = function() { // ES6
            //--
            let uuid = String((tseed++).toString(36)).toUpperCase().substr(-10); // if longer than 10 chars, take the last 10 chars only
            //--
            if (uuid.length < 10) {
                for (let i = 0; i < uuid.length; i++) { // left pad with zeroes
                    if (uuid.length < 10) {
                        uuid = String('0' + uuid);
                    } else {
                        break;
                    } //end if else
                } //end for
            } //end if
            //--
            return String(uuid);
            //--
        }; //END
        _C$.uuid = uuid; // export


        /*
         * Add an Element to a List
         *
         * @private internal development only
         *
         * @memberof smartJ$Utils
         * @method addToList
         * @static
         *
         * @param 	{String} 	newVal 		The new val to add to the List
         * @param 	{String} 	textList 	The string List to add newVal at
         * @param 	{String} 	splitBy 	The string separator, any of: , ;
         * @return 	{String} 				The processed string as List separed by separator
         */
        const addToList = function(newVal, textList, splitBy) { // ES6
            //--
            newVal = String((newVal == undefined) ? '' : stringTrim(newVal));
            if (newVal == '') {
                return '';
            } //end if
            //--
            textList = String((textList == undefined) ? '' : textList);
            //--
            let terms = [];
            //--
            splitBy = String((splitBy == undefined) ? '' : splitBy);
            switch (splitBy) {
                case ',':
                    terms = stringSplitbyComma(textList); // Array
                    break;
                case ';':
                    terms = stringSplitbySemicolon(textList); // Array
                    break;
                default:
                    _p$.error(_N$, 'ERR: addToList: Invalid splitBy separator. Must be any of [,;] and is:', splitBy);
                    return '';
            } //end switch
            //--
            let found = false;
            if (terms.length > 0) {
                terms.pop(); // remove the current input
                for (let i = 0; i < terms.length; i++) {
                    if (terms[i] == newVal) {
                        found = true;
                        break;
                    } //end if
                } //end for
            } //end if
            if (!found) {
                terms.push(newVal); // add the selected item
            } //end if
            terms.push(''); // add placeholder to get the comma-and-space at the end
            //--
            return String(terms.join(splitBy + ' '));
            //--
        }; //END
        _C$.addToList = addToList; // export, hidden


        /**
         * Sort a stack (array / object / property) using String Sort algorithm
         *
         * @memberof smartJ$Utils
         * @method textSort
         * @static
         *
         * @param 	{Mixed} 	property 		The stack to be sorted
         * @param 	{Boolean} 	useLocale 		*Optional* default is FALSE ; if TRUE will try sort using locales and if fail will fallback on raw string comparison ; if FALSE will use the default string comparison
         * @return 	{Method} 					The stack sort method
         */
        const textSort = function(property, useLocale = false) { // ES6
            //--
            return (a, b) => {
                //--
                a[property] = String(a[property]);
                b[property] = String(b[property]);
                //--
                let comparer = 0;
                let localeFailed = false;
                if (useLocale === true) {
                    try { // a better compare using locales, if n/a fallback to non-locale compare
                        comparer = a[property].localeCompare(b[property]);
                        if (comparer < 0) {
                            comparer = -1;
                        } else if (comparer > 0) {
                            comparer = 1;
                        } else {
                            comparer = 0;
                        } //end if else
                    } catch (e) { // non-locale compare
                        localeFailed = true;
                    } //end try catch
                } //end if
                if ((useLocale !== true) || (localeFailed === true)) {
                    comparer = ((a[property] < b[property]) ? -1 : (a[property] > b[property])) ? 1 : 0;
                } //end if
                //--
                return comparer; // mixed
                //--
            } //end
            //--
        }; //END
        _C$.textSort = textSort; // export


        /**
         * Sort a stack (array / object / property) using Numeric Sort algorithm
         *
         * @memberof smartJ$Utils
         * @method numericSort
         * @static
         * @arrow
         *
         * @param 	{Mixed} 	property 		The stack to be sorted
         * @return 	{Method} 					The stack sort method
         */
        const numericSort = (property) => { // ES6
            //--
            return (a, b) => {
                //--
                if ((!isFiniteNumber(a[property])) || (!isFiniteNumber(b[property]))) {
                    return 0;
                } //end if
                //--
                if (a[property] > b[property]) {
                    return 1;
                } else if (a[property] < b[property]) {
                    return -1;
                } else {
                    return 0;
                } //end if else
                //--
            } //end
            //--
        }; //END
        _C$.numericSort = numericSort; // export


        /**
         * Add URL Suffix (to a standard RFC3986 URL) as: script.php?a=b&C=D&e=%20d
         *
         * @memberof smartJ$Utils
         * @method url_add_suffix
         * @static
         * @arrow
         *
         * @param 	{String} 	url 			The base URL to use as prefix like: script.php or script.php?a=b&c=d or empty
         * @param 	{String} 	suffix 			A RFC3986 URL segment like: a=b or E=%20d (without ? or not starting with & as they will be detected if need append ? or &; variable values must be encoded using escape_url() RFC3986)
         * @return 	{String} 					The prepared URL in the standard RFC3986 format (all values are escaped using escape_url() to be Unicode full compliant
         */
        const url_add_suffix = function(url, suffix) { // ES6
            //--
            url = stringPureVal(url, true); // cast to string, trim
            //--
            suffix = stringPureVal(suffix, true); // cast to string, trim
            if (stringStartsWith(suffix, '?') || stringStartsWith(suffix, '&')) {
                suffix = stringPureVal(suffix.substring(1), true); // cast to string, trim
            } //end if
            if (suffix == '') {
                return String(url);
            } //end if
            //--
            let separator = '';
            if (stringContains(url, '?')) {
                separator = '&';
            } else {
                separator = '?';
            } //end if else
            //--
            return String(url + separator + suffix);
            //--
        }; //END
        _C$.url_add_suffix = url_add_suffix; // export


        /**
         * Creates a Slug (URL safe slug) from a string
         *
         * @memberof smartJ$Utils
         * @method create_slug
         * @static
         * @arrow
         *
         * @param 	{String} 	str 			The string to be processed
         * @param 	{Boolean} 	lowercase 		*OPTIONAL* If TRUE will return the slug with enforced lowercase characters ; DEFAULT is FALSE
         * @param 	{Integer} 	maxlen			*OPTIONAL* If a positive value greater than zero is supplied here the slug max length will be constrained to the value
         * @return 	{String} 					The slug which will contain only: a-z 0-9 _ - (A-Z will be converted to a-z if lowercase is enforced)
         */
        const create_slug = (str, lowercase = false, maxlen = 0) => { // ES6
            //--
            str = stringPureVal(str, true); // cast to string, trim
            if (str == '') {
                return '';
            } //end if
            //--
            str = String(deaccent_str(stringTrim(str)));
            str = str.replace(/[^a-zA-Z0-9_\-]/g, '-');
            str = str.replace(/[\-]+/g, '-'); // suppress multiple -
            str = str.replace(/^[\-]+/, '').replace(/[\-]+$/, '');
            str = String(stringTrim(str));
            //--
            if (lowercase === true) {
                str = str.toLowerCase();
            } //end if
            //--
            maxlen = format_number_int(maxlen, false);
            if (maxlen > 0) {
                str = str.substr(0, maxlen);
                str = str.replace(/(\-)+$/, '');
            } //end if
            //--
            return String(str);
            //--
        }; //END
        _C$.create_slug = create_slug; // export


        /**
         * Creates a compliant HTML-ID (HTML ID used for HTML elements) from a string
         *
         * @memberof smartJ$Utils
         * @method create_htmid
         * @static
         * @arrow
         *
         * @param 	{String} 	str 			The string to be processed
         * @return 	{String} 					The HTML-ID which will contain only: a-z A-Z 0-9 _ -
         */
        const create_htmid = (str) => { // ES6
            //--
            str = stringPureVal(str, true); // cast to string, trim
            if (str == '') {
                return '';
            } //end if
            //--
            str = str.replace(/[^a-zA-Z0-9_\-]/g, '');
            str = stringTrim(str);
            //--
            return String(str);
            //--
        }; //END
        _C$.create_htmid = create_htmid; // export


        /**
         * Creates a compliant Js-Var (JavaScript Variable Name) from a string
         *
         * @memberof smartJ$Utils
         * @method create_jsvar
         * @static
         * @arrow
         *
         * @param 	{String} 	str 			The string to be processed
         * @return 	{String} 					The Js-Var which will contain only: a-z A-Z 0-9 _ $
         */
        const create_jsvar = (str) => { // ES6
            //--
            str = stringPureVal(str, true); // cast to string, trim
            if (str == '') {
                return '';
            } //end if
            //--
            str = str.replace(/[^a-zA-Z0-9_\$]/g, '');
            str = String(stringTrim(str));
            //--
            return String(str);
            //--
        }; //END
        _C$.create_jsvar = create_jsvar; // export


        /*
         * Revert from Prepare a value or a template by escaping syntax
         *
         * @memberof smartJ$Utils
         * @method revertNosyntaxContentMarkersTpl
         * @static
         * @arrow
         *
         * @param 	{String} 	template 	The string template with markers
         * @return 	{String} 				The processed string
         */
        const revertNosyntaxContentMarkersTpl = (tpl) => { // ES6
            //-- IMPORTANT: all the MarkerTPL Syntax has been written as '' + '' or \% in regex to allow this Js to be embedded in HTML TPLs without interfering with that syntax
            if (typeof(tpl) !== 'string') {
                return '';
            } //end if
            //--
            tpl = String(tpl);
            //-- keep as expr to allow embedding this js file in other HTML TPLs
            tpl = stringReplaceAll('［' + '###', '[' + '###', tpl);
            tpl = stringReplaceAll('###' + '］', '###' + ']', tpl);
            tpl = stringReplaceAll('［' + '%%%', '[' + '%%%', tpl);
            tpl = stringReplaceAll('%%%' + '］', '%%%' + ']', tpl);
            tpl = stringReplaceAll('［' + '@@@', '[' + '@@@', tpl);
            tpl = stringReplaceAll('@@@' + '］', '@@@' + ']', tpl);
            tpl = stringReplaceAll('［' + ':::', '[' + ':::', tpl);
            tpl = stringReplaceAll(':::' + '］', ':::' + ']', tpl);
            //--
            return String(tpl);
            //--
        }; //END
        _C$.revertNosyntaxContentMarkersTpl = revertNosyntaxContentMarkersTpl; // export, hidden


        /*
         * Prepare a value or a template by escaping syntax
         *
         * @memberof smartJ$Utils
         * @method prepareNosyntaxContentMarkersTpl
         * @static
         * @arrow
         *
         * @param 	{String} 	template 	The string template with markers
         * @return 	{String} 				The processed string
         */
        const prepareNosyntaxContentMarkersTpl = (tpl) => { // ES6
            //-- IMPORTANT: all the MarkerTPL Syntax has been written as '' + '' or \% in regex to allow this Js to be embedded in HTML TPLs without interfering with that syntax
            if (typeof(tpl) !== 'string') {
                return '';
            } //end if
            //--
            tpl = String(tpl);
            //--
            tpl = stringReplaceAll('[' + '###', '［' + '###', tpl);
            tpl = stringReplaceAll('###' + ']', '###' + '］', tpl);
            tpl = stringReplaceAll('[' + '%%%', '［' + '%%%', tpl);
            tpl = stringReplaceAll('%%%' + ']', '%%%' + '］', tpl);
            tpl = stringReplaceAll('[' + '@@@', '［' + '@@@', tpl);
            tpl = stringReplaceAll('@@@' + ']', '@@@' + '］', tpl);
            tpl = stringReplaceAll('[' + ':::', '［' + ':::', tpl);
            tpl = stringReplaceAll(':::' + ']', ':::' + '］', tpl);
            //--
            return String(tpl);
            //--
        }; //END
        _C$.prepareNosyntaxContentMarkersTpl = prepareNosyntaxContentMarkersTpl; // export, hidden


        /*
         * Prepare a HTML template for display in no-conflict mode: no syntax / markers will be parsed
         * To keep the markers and syntax as-is but avoiding conflicting with real markers / syntax it will encode as HTML Entities the following syntax patterns: [ ] # % @
         * @hint !!! It is intended for very special usage ... !!!
         *
         * @memberof smartJ$Utils
         * @method prepareNosyntaxHtmlMarkersTpl
         * @static
         * @arrow
         *
         * @param 	{String} 	template 	The string template with markers
         * @return 	{String} 				The processed string
         */
        const prepareNosyntaxHtmlMarkersTpl = (tpl) => { // ES6
            //-- IMPORTANT: all the MarkerTPL Syntax has been written as '' + '' or \% in regex to allow this Js to be embedded in HTML TPLs without interfering with that syntax
            if (typeof(tpl) !== 'string') {
                return '';
            } //end if
            //--
            tpl = String(tpl);
            //--
            tpl = stringReplaceAll('[' + '###', '&lbrack;&num;&num;&num;', tpl);
            tpl = stringReplaceAll('###' + ']', '&num;&num;&num;&rbrack;', tpl);
            tpl = stringReplaceAll('[' + '%%%', '&lbrack;&percnt;&percnt;&percnt;', tpl);
            tpl = stringReplaceAll('%%%' + ']', '&percnt;&percnt;&percnt;&rbrack;', tpl);
            tpl = stringReplaceAll('[' + '@@@', '&lbrack;&commat;&commat;&commat;', tpl);
            tpl = stringReplaceAll('@@@' + ']', '&commat;&commat;&commat;&rbrack;', tpl);
            tpl = stringReplaceAll('[' + ':::', '&lbrack;&colon;&colon;&colon;', tpl);
            tpl = stringReplaceAll(':::' + ']', '&colon;&colon;&colon;&rbrack;', tpl);
            //--
            tpl = stringReplaceAll('［' + '###', '&lbrack;&num;&num;&num;', tpl);
            tpl = stringReplaceAll('###' + '］', '&num;&num;&num;&rbrack;', tpl);
            tpl = stringReplaceAll('［' + '%%%', '&lbrack;&percnt;&percnt;&percnt;', tpl);
            tpl = stringReplaceAll('%%%' + '］', '&percnt;&percnt;&percnt;&rbrack;', tpl);
            tpl = stringReplaceAll('［' + '@@@', '&lbrack;&commat;&commat;&commat;', tpl);
            tpl = stringReplaceAll('@@@' + '］', '&commat;&commat;&commat;&rbrack;', tpl);
            tpl = stringReplaceAll('［' + ':::', '&lbrack;&colon;&colon;&colon;', tpl);
            tpl = stringReplaceAll(':::' + '］', '&colon;&colon;&colon;&rbrack;', tpl);
            //--
            return String(tpl);
            //--
        }; //END
        _C$.prepareNosyntaxHtmlMarkersTpl = prepareNosyntaxHtmlMarkersTpl; // export, hidden


        /**
         * Render Simple Marker-TPL Template + Comments + Specials (only markers replacements with escaping or processing syntax and support for comments and special replacements: SPACE, TAB, R, N ; no support for IF / LOOP / INCLUDE syntax since the js regex is too simplistic and it can be implemented using real js code)
         * It is compatible just with the REPLACE MARKERS of Smart.Framework PHP server-side Marker-TPL Templating for substitutions on client-side, except the extended syntax as IF/LOOP/INCLUDE.
         * @hint To be used together with the server-side Marker-TPL templating (ex; PHP), to avoid the server-side markers to be rendered ; ex: `［###MARKER###］` will need the template to be escape_url+escape_js ; for this purpose the 3rd param can be set to true: isEncoded = TRUE
         *
         * @example
         * // sample client side MarkersTPL rendering
         * // HINTS on using |js escaping:
         * // - never use a marker inside javascript backticks (`) ; ex: don't do this: const test = `［###TEST|js###］`; // it will raise js code compile errors if a string contains a backtick (`) ... this is because the backticks are not escaped by json escaping ...
         * // - when using TPL markers inside javascript single quotes (') or double quotes (") just use the escaping: |js ; ex: const test = '［###TEST|js###］'; const test2 = "［###TEST|js###］";
         * // - if the context of javascript is using escaped single quotes ('\'\'') or escaped double quotes ("\"\""), if possible use ("''") or ('""') as ("'［###TEST|js###］'") or ('"［###TEST|js###］"') is OK ; but ('\'［###TEST|js###］\'') or ("\"［###TEST|js###］\"") IS NOT OK because the js code will compile but when the js will evaluate the context will raise an error as the strings will terminate premature if they contain a single or double quote ; also using backticks like (`'［###TEST|js###］'`) or (`"［###TEST|js###］"`) is wrong, it is explained above ...
         * // - if the javascript context is using escaped single quotes, have to use: ('\'［###TEST|js|js###］\''), which is OK
         * // - if the javascript context is using escaped double quotes, have to use: ("\"［###TEST|js|js###］"') + "\""), which is OK
         * // see below a real life situation when have to use this:
         * const question = '［###QUESTION|js###］'; // OK
         * console.log('Question:', question);
         * const tpl = '<div onclick="let Question=\'［###QUESTION|js|js###］\'; alert(Question);">［###TITLE|html|js###］</div>'; // the TPL ; OK because is using double |js escaping with single quotes escaping to avoid js errors when js is evaluating the string ...
         * const tpl2 = '<div onclick="alert(question);">［###TITLE|html|js###］</div>'; // alternate TPL ; OK, will use the global variable / constant question so making like this avoid adding escaped single or double quotes
         * // it is a common situation when building html syntax with javascript to have combined single quotes (javascript) with double quotes (html), so adding in a html attribute another javascript have no other solution than using again single quotes but escaped like \' and if a marker is placed, must add like: let html = '<button onClick="let test = \'［###TEST|js|js###］\';">Test</button>'; // notice the double escaping
         * let html = smartJ$Utils.renderMarkersTpl( // render TPL
         * 		tpl,
         * 		{
         * 			'TITLE': 'A Title',
         * 			'QUESTION': 'A Question'
         * 		}
         * jQuery('body').append(html); // display TPL
         *
         * @memberof smartJ$Utils
         * @method renderMarkersTpl
         * @static
         *
         * @param 	{String} 	template 		The string template with markers
         * @param 	{ArrayObj} 	arrobj 			The Object-Array with marker replacements as { 'MAR.KER_1' => 'Value 1', 'MARKER-2' => 'Value 2', ... }
         * @param 	{Boolean} 	isEncoded 		If TRUE will do a decoding over template string (apply if TPL is sent as encoded from server-side or directly in html page)
         * @param 	{Boolean} 	revertSyntax 	If TRUE will do a revertNosyntaxContentMarkersTpl() over template string (apply if TPLs is sent with syntax escaped)
         * @return 	{String} 					The processed string
         */
        const renderMarkersTpl = function(template, arrobj, isEncoded = false, revertSyntax = false) { // ES6
            //-- syntax: r.20220331 ; IMPORTANT: all the MarkerTPL Syntax has been written as '' + '' or \% in regex to allow this Js to be embedded in HTML TPLs without interfering with that syntax
            if ((typeof(template) === 'string') && (typeof(arrobj) === 'object')) {
                //--
                if (isEncoded === true) {
                    template = String(decodeURIComponent(template));
                } //end if
                if (revertSyntax === true) {
                    template = String(revertNosyntaxContentMarkersTpl(template));
                } //end if
                //--
                template = stringTrim(template);
                //-- remove comments: javascript regex miss the regex flags: s = single line: Dot matches newline characters ; U = Ungreedy: The match becomes lazy by default ; Now a ? following a quantifier makes it greedy
                // because missing the single line dot match and ungreedy is almost impossible to solve it with a regex in an optimum way, thus we use the trick :-)
                // because missing the /s flag, the extra \S have to be added to the \s to match new lines and the (.*) have become ([\s\S^]*)
                // because missing the /U flag, missing ungreedy, we need to split/join to solve it
                if (stringContains(template, '[' + '%%%COMMENT%%%' + ']') && stringContains(template, '[' + '%%%/COMMENT%%%' + ']')) {
                    let arr_comments = [];
                    arr_comments = template.split('[' + '%%%COMMENT%%%' + ']');
                    for (let i = 0; i < arr_comments.length; i++) {
                        if (stringContains(arr_comments[i], '[' + '%%%/COMMENT%%%' + ']')) {
                            arr_comments[i] = '[' + '%%%COMMENT%%%' + ']' + arr_comments[i];
                            arr_comments[i] = arr_comments[i].replace(/[\s\S]?\[\%\%\%COMMENT\%\%\%\]([\s\S]*?)\[\%\%\%\/COMMENT\%\%\%\][\s\S]?/g, '');
                        } //end if
                    } //end for
                    template = stringTrim(arr_comments.join(''));
                    arr_comments = null;
                } //end if
                //-- replace markers
                if (template != '') {
                    //--
                    const regexp = /\[\#\#\#([A-Z0-9_\-\.]+)((\|[a-z0-9]+)*)\#\#\#\]/g; // {{{SYNC-REGEX-MARKER-TEMPLATES}}}
                    //--
                    let markers = stringRegexMatchAll(template, regexp);
                    //	if(debug) {
                    //		_p$.log(_N$, markers);
                    //	} //end if
                    //--
                    if (markers.length) {
                        //--
                        let marker, escaping;
                        let tmp_marker_val, tmp_marker_id, tmp_marker_key, tmp_marker_esc, tmp_marker_arr_esc;
                        //--
                        let xnum, xlen, jsonObj;
                        //--
                        for (let i = 0; i < markers.length; i++) {
                            //--
                            marker = markers[i]; // expects array
                            //	if(debug) {
                            //		_p$.log(_N$, JSON.stringify(marker, null, 2));
                            //	} //end if
                            //--
                            if (marker.length) {
                                //--
                                tmp_marker_val = ''; // just initialize
                                tmp_marker_id = marker[0] ? String(marker[0]) : ''; // ［###THE-MARKER|escapings...###］
                                tmp_marker_key = marker[1] ? String(marker[1]) : ''; // THE-MARKER
                                tmp_marker_esc = marker[2] ? String(marker[2]) : ''; // |escaping1(|escaping2...|escaping99)
                                tmp_marker_arr_esc = []; // just initialize
                                //--
                                if ((tmp_marker_id != null) && (tmp_marker_id != '') && (tmp_marker_key != null) && (tmp_marker_key != '') && stringContains(template, tmp_marker_id)) { // check if exists because it does replaceAll on a cycle so another cycle can run without scope !
                                    //--
                                    //	if(debug) {
                                    //		_p$.log(_N$, 'Marker Found: ' + tmp_marker_id + ' :: ' + tmp_marker_key);
                                    //	} //end if
                                    //--
                                    if (tmp_marker_key in arrobj) {
                                        //-- prepare val from input array
                                        tmp_marker_val = arrobj[tmp_marker_key] ? arrobj[tmp_marker_key] : '';
                                        tmp_marker_val = String(tmp_marker_val);
                                        //-- protect against cascade recursion of syntax by escaping all the syntax in value
                                        tmp_marker_val = String(prepareNosyntaxContentMarkersTpl(tmp_marker_val));
                                        //--
                                        if (tmp_marker_esc) { // if non-empty before removing leading | ; else no escapings
                                            //--
                                            if (stringStartsWith(tmp_marker_esc, '|')) { // if contains leading |
                                                tmp_marker_esc = tmp_marker_esc.substr(1); // remove leading |
                                            } //end if
                                            //--
                                            if (tmp_marker_esc) { // if non-empty after removing leading | ; else no escapings
                                                //--
                                                tmp_marker_arr_esc = tmp_marker_esc.split(/\|/); // Array, split by |
                                                //	if(debug) {
                                                //		_p$.log(_N$, JSON.stringify(tmp_marker_arr_esc, null, 2));
                                                //	} //end if
                                                //--
                                                if (tmp_marker_arr_esc.length) {
                                                    //--
                                                    for (let j = 0; j < tmp_marker_arr_esc.length; j++) {
                                                        //--
                                                        escaping = String('|' + String(tmp_marker_arr_esc[j]));
                                                        //	if(debug) {
                                                        //		_p$.log(_N$, tmp_marker_id + ' / ' + escaping);
                                                        //	} //end if
                                                        //--
                                                        if (escaping == '|bool') { // Boolean
                                                            if (tmp_marker_val) {
                                                                tmp_marker_val = 'true';
                                                            } else {
                                                                tmp_marker_val = 'false';
                                                            } //end if else
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format Bool: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|int') { // Integer
                                                            tmp_marker_val = String(format_number_int(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format Int: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping.substring(0, 4) == '|dec') { // Dec (1..4)
                                                            xnum = format_number_int(escaping.substring(4));
                                                            if (xnum < 1) {
                                                                xnum = 1;
                                                            } //end if
                                                            if (xnum > 4) {
                                                                xnum = 4;
                                                            } //end if
                                                            tmp_marker_val = String(format_number_dec(tmp_marker_val, xnum, true, false)); // allow negatives, do not discard trailing zeroes
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format Dec[' + xnum + ']: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                            xnum = null;
                                                        } else if (escaping == '|num') { // Number (Float / Decimal / Integer)
                                                            tmp_marker_val = String(format_number_float(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format Number: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|idtxt') { // id_txt: Id-Txt
                                                            tmp_marker_val = String(stringReplaceAll('_', '-', tmp_marker_val));
                                                            tmp_marker_val = String(stringUcWords(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format IdTxt: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|slug') { // Slug: a-zA-Z0-9_- / - / -- : -
                                                            tmp_marker_val = String(create_slug(tmp_marker_val, false)); // do not apply strtolower as it can be later combined with |lower flag
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format SLUG: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|htmid') { // HTML-ID: a-zA-Z0-9_-
                                                            tmp_marker_val = String(create_htmid(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format HTML-ID: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|jsvar') { // JS-Variable: a-zA-Z0-9_$
                                                            tmp_marker_val = String(create_jsvar(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format JS-VAR: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if ((escaping.substring(0, 7) == '|substr') || (escaping.substring(0, 7) == '|subtxt')) { // Sub(String|Text) (0,num)
                                                            xnum = format_number_int(escaping.substring(7));
                                                            if (xnum < 1) {
                                                                xnum = 1;
                                                            } //end if
                                                            if (xnum > 65535) {
                                                                xnum = 65535;
                                                            } //end if
                                                            if (xnum >= 1 && xnum <= 65535) {
                                                                xlen = tmp_marker_val.length;
                                                                if (escaping.substring(0, 7) == '|subtxt') {
                                                                    if (xnum < 5) {
                                                                        xnum = 5;
                                                                    } //end if
                                                                    xnum = xnum - 3;
                                                                    if (xlen > xnum) {
                                                                        tmp_marker_val = tmp_marker_val.substring(0, xnum);
                                                                        tmp_marker_val = tmp_marker_val.replace(/\s+?(\S+)?$/, ''); // {{{SYNC-REGEX-TEXT-CUTOFF}}}
                                                                        tmp_marker_val = String(tmp_marker_val) + '...';
                                                                    } //end if
                                                                    //	if(debug) {
                                                                    //		_p$.log(_N$, 'Marker Sub-Text(' + xnum + '): ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                                    //	} //end if
                                                                } else { // '|substr'
                                                                    if (xlen > xnum) {
                                                                        tmp_marker_val = tmp_marker_val.substring(0, xnum);
                                                                    } //end if
                                                                    //	if(debug) {
                                                                    //		_p$.log(_N$, 'Marker Sub-String(' + xnum + '): ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                                    //	} //end if
                                                                } //end if
                                                                xlen = null;
                                                            } //end if
                                                            xnum = null;
                                                            tmp_marker_val = String(tmp_marker_val);
                                                        } else if (escaping == '|lower') { // apply lowercase
                                                            tmp_marker_val = String(tmp_marker_val).toLowerCase();
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format Apply LowerCase: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|upper') { // apply uppercase
                                                            tmp_marker_val = String(tmp_marker_val).toUpperCase();
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format Apply UpperCase: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|ucfirst') { // apply uppercase first character
                                                            tmp_marker_val = String(stringUcFirst(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format Apply UcFirst: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|ucwords') { // apply uppercase on each word
                                                            tmp_marker_val = String(stringUcWords(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format Apply UcWords: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|trim') { // apply trim
                                                            tmp_marker_val = String(stringTrim(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format Apply Trim: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|url') { // escape URL
                                                            tmp_marker_val = String(escape_url(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker URL-Escape: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|json') { // format as Json Data ; expects pure JSON !!!
                                                            jsonObj = null;
                                                            try {
                                                                jsonObj = JSON.parse(tmp_marker_val); // it MUST be JSON !
                                                            } catch (err) {
                                                                jsonObj = null;
                                                            } //end try catch
                                                            tmp_marker_val = JSON.stringify(jsonObj, null, null); // ensure no pretty print or json replacer is used
                                                            tmp_marker_val = String(tmp_marker_val); // force string
                                                            // Fixes: the JSON stringify does not make the JSON to be HTML-Safe, thus we need several minimal replacements: https://www.drupal.org/node/479368 + escape /
                                                            tmp_marker_val = tmp_marker_val.replace(/[\u0026]/g, '\\u0026'); // & 	JSON_HEX_AMP
                                                            tmp_marker_val = tmp_marker_val.replace(/[\u003C]/g, '\\u003C'); // < 	JSON_HEX_TAG (use uppercase as in PHP)
                                                            tmp_marker_val = tmp_marker_val.replace(/[\u003E]/g, '\\u003E'); // > 	JSON_HEX_TAG (use uppercase as in PHP)
                                                            tmp_marker_val = tmp_marker_val.replace(/[\/]/g, '\\/'); // / 	JSON_UNESCAPED_SLASHES
                                                            // the JSON string will not be 100% like the one produced via PHP with HTML-Safe arguments but at least have the minimum escapes to avoid conflicting HTML tags
                                                            tmp_marker_val = String(stringTrim(tmp_marker_val));
                                                            if (tmp_marker_val == '') {
                                                                tmp_marker_val = 'null'; // ensure a minimal json as null for empty string if no expr !
                                                            } //end if
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Format JSON: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|js') { // Escape JS
                                                            tmp_marker_val = String(escape_js(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker JS-Escape: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|html') { // Escape HTML
                                                            tmp_marker_val = String(escape_html(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker HTML-Escape: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|css') { // Escape CSS
                                                            tmp_marker_val = String(escape_css(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker CSS-Escape: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|nl2br') { // Format NL2BR
                                                            tmp_marker_val = String(nl2br(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker NL2BR-Reflow: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|smartlist') { // Apply SmartList Fix Replacements ; {{{SYNC-SMARTLIST-BRACKET-REPLACEMENTS}}}
                                                            tmp_marker_val = String(stringReplaceAll('<', '‹', tmp_marker_val));
                                                            tmp_marker_val = String(stringReplaceAll('>', '›', tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker SmartList Fixes: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|syntaxhtml') { // fix back markers tpl escapings in html
                                                            tmp_marker_val = String(prepareNosyntaxHtmlMarkersTpl(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Syntax-Html-Escape: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                        } else if (escaping == '|hex') { // Apply Bin2Hex Encode
                                                            tmp_marker_val = String(bin2hex(tmp_marker_val));
                                                            //	if(debug) {
                                                            //		_p$.log(_N$, 'Marker Bin2Hex: ' + tmp_marker_id + ' :: ' + tmp_marker_key + ' #' + j + ' ' + escaping + ' @ ' + tmp_marker_val);
                                                            //	} //end if
                                                            //--
                                                            // '|b64' 	:: !! this is not exported to JS, it a bit heavyweight for a light templating engine like js ..., and if needed it can build the syntax using concatenation with a b64 encoded string that can be done in js, using smart crypt utils / b64
                                                            // '|sha1' 	:: !! this is not exported to JS, it a bit heavyweight for a light templating engine like js ..., and if needed it can build the syntax using concatenation with a sha1 hashed string that can be done in js, using smart crypt utils / sha1
                                                            //--
                                                        } else {
                                                            _p$.warn(_N$, 'WARN: renderMarkersTpl: {### Invalid or Undefined Escaping for Marker: ' + escaping + ' - detected in Replacement Key: ' + tmp_marker_id + ' @ ' + tmp_marker_val + ' detected in Template: ^' + '\n' + template.substr(0, 512) + '$(0..512)... ###}');
                                                        } //end if else
                                                        //--
                                                    } //end for
                                                    //--
                                                } //end if
                                                //--
                                            } //end if else
                                            //--
                                        } //end if
                                        //--
                                        template = stringReplaceAll(tmp_marker_id, tmp_marker_val, template);
                                        //--
                                    } //end if
                                    //--
                                } //end if
                            } //end if
                            //--
                            marker = null;
                            escaping = null;
                            tmp_marker_val = null;
                            tmp_marker_id = null;
                            tmp_marker_key = null;
                            tmp_marker_esc = null;
                            tmp_marker_arr_esc = null;
                            //--
                        } //end for
                        //--
                    } //end if
                    //--
                    markers = null;
                    //-- replace specials: Square-Brackets(L/R) R N TAB SPACE
                    if (stringContains(template, '[' + '%%%|')) {
                        template = stringReplaceAll('[' + '%%%|SB-L%%%' + ']', '［', template);
                        template = stringReplaceAll('[' + '%%%|SB-R%%%' + ']', '］', template);
                        template = stringReplaceAll('[' + '%%%|R%%%' + ']', '\r', template);
                        template = stringReplaceAll('[' + '%%%|N%%%' + ']', '\n', template);
                        template = stringReplaceAll('[' + '%%%|T%%%' + ']', '\t', template);
                        template = stringReplaceAll('[' + '%%%|SPACE%%%' + ']', ' ', template);
                    } //end if
                    //--
                    if (stringContains(template, '[' + '###')) {
                        _p$.warn(_N$, 'WARN: renderMarkersTpl:', stringRegexMatchAll(template, regexp), '{### Undefined Markers detected in Template:' + '\n' + template.substr(0, 512) + '$(0..512)... ###}');
                    } //end if
                    if (stringContains(template, '[' + '%%%')) {
                        _p$.warn(_N$, 'WARN: renderMarkersTpl: {### Undefined Marker Syntax detected in Template: ^' + '\n' + template.substr(0, 512) + '$(0..512)... ###}');
                    } //end if
                    if (stringContains(template, '[' + '@@@')) {
                        _p$.warn(_N$, 'WARN: renderMarkersTpl: {### Undefined Marker Sub-Templates detected in Template: ^' + '\n' + template.substr(0, 512) + '$(0..512)... ###}');
                    } //end if
                    //--
                } //end if else
                //--
            } else {
                //--
                _p$.error(_N$, 'ERR: renderMarkersTpl: {### Invalid Marker-TPL Arguments ###}');
                template = '';
                //--
            } //end if
            //--
            return String(template); // fix to return empty string instead of null [OK]
            //--
        }; //END
        _C$.renderMarkersTpl = renderMarkersTpl; // export


    }
}; //END CLASS

smartJ$Utils.secureClass(); // implements class security

if (typeof(window) != 'undefined') {
    window.smartJ$Utils = smartJ$Utils; // global export
} //end if

//==================================================================
//==================================================================

// #END

// ===== date_utils.js

// [LIB - Smart.Framework / JS / DateUtils]
// (c) 2006-2022 unix-world.org - all rights reserved
// r.8.7 / smart.framework.v.8.7

// DEPENDS: smartJ$Utils

//==================================================================
// The code is released under the BSD License. Copyright (c) unix-world.org
// The file contains portions of code from:
//	- https://github.com/joshduck/simple-day # A simple library for working with calendar days (YYYY-MM-DD) as plain old JavaScript objects.
//	- https://www.npmjs.com/package/date-offset @ http://howardhinnant.github.io/date_algorithms.html # A simple library for converting Gregorian dates to an integer offset.
//==================================================================

//================== [ES6]

/**
 * CLASS :: Smart DateUtils (ES6, Strict Mode)
 *
 * @package Sf.Javascript:Core
 *
 * @requires		smartJ$Utils
 *
 * @desc Date Utils class for Javascript
 * @author unix-world.org
 * @license BSD
 * @file date_utils.js
 * @version 20220907
 * @class smartJ$Date
 * @static
 * @frozen
 *
 * @example
 * let d = new Date();
 * console.log(JSON.stringify(d, null, 2));
 *
 * let dz = smartJ$Date.standardizeDate(d);
 * console.log(JSON.stringify(dz, null, 2));
 *
 * let ds = smartJ$Date.standardizeDate({ year: d.getFullYear(), month: d.getMonth()+1, day: d.getDate() });
 * console.log(JSON.stringify(ds, null, 2));
 *
 * let iso = smartJ$Date.getIsoDate(ds);
 * console.log(iso);
 *
 * let iso2 = smartJ$Date.getIsoDate(ds, true);
 * console.log(iso2);
 *
 * let d1 = smartJ$Date.createSafeDate(d.getFullYear(), d.getMonth()+1, d.getDate());
 * console.log(JSON.stringify(d1, null, 2));
 *
 * let d2 = smartJ$Date.createSafeDate(d.getFullYear(), (d.getMonth()+1)+3, d.getDate());
 * console.log(JSON.stringify(d2, null, 2));
 *
 * let o = smartJ$Date.calculateDaysOffset(d1, d2);
 * console.log(o);
 *
 * let ox = smartJ$Date.calculateDaysOffset(d2, d1);
 * console.log(ox);
 *
 * let m = smartJ$Date.calculateMonthsOffset(d1, d2);
 * console.log(m);
 *
 * let mx = smartJ$Date.calculateMonthsOffset(d2, d1);
 * console.log(mx);
 *
 * let a1 = smartJ$Date.addYears(ds, 1);
 * console.log(JSON.stringify(a1, null, 2));
 *
 * let a2 = smartJ$Date.addMonths(ds, 12);
 * console.log(JSON.stringify(a2, null, 2));
 *
 * let a3 = smartJ$Date.addDays(ds, 365);
 * console.log(JSON.stringify(a3, null, 2));
 *
 * let fd = smartJ$Date.formatDate('yy-mm-dd', d);
 * console.log(fd);
 *
 * let dd = smartJ$Date.determineDate(d);
 * console.log(JSON.stringify(dd, null, 2));
 */
const smartJ$Date = new class {
    constructor() { // STATIC CLASS
        'use strict';
        const _N$ = 'smartJ$Date';

        // :: static
        const _C$ = this; // self referencing

        const _p$ = console;

        let SECURED = false;
        _C$.secureClass = () => { // implements class security
            if (SECURED === true) {
                _p$.warn(_N$, 'Class is already SECURED');
            } else {
                SECURED = true;
                Object.freeze(_C$);
            } //end if
        }; //END

        const _Utils$ = smartJ$Utils;


        /**
         * Create a Safe Date Object
         *
         * @memberof smartJ$Date
         * @method createSafeDate
         * @static
         * @arrow
         *
         * @param 	{Integer} 	year 	The Raw Year: YYYY
         * @param 	{Integer} 	month 	The Raw Month: 1..12 ; if wrong will fix ahead or behind
         * @param 	{Integer} 	day 	The Raw Day: 1..31 ; if wrong will fix ahead or behind
         * @return 	{Object} 			Normalized Date Object as: { year: YYYY, month: 1..12, day: 1..31 }
         */
        const createSafeDate = (year, month, day) => { // ES6
            //--
            year = _Utils$.format_number_int(year); // allow negative
            month = _Utils$.format_number_int(month, false);
            day = _Utils$.format_number_int(day, false);
            //--
            return normalizeAndClone({
                year: year,
                month: month,
                day: day
            });
            //--
        }; //END
        _C$.createSafeDate = createSafeDate; // export


        /**
         * Normalize a Date Object
         *
         * @memberof smartJ$Date
         * @method standardizeDate
         * @static
         *
         * @param 	{Object} 	date 	The instanceof Date() or the Raw Date Object that need to be safe fixed as { year: YYYY, month: 1..12, day: 1..31 }
         * @return 	{Object} 			Normalized Date Object as: { year: YYYY, month: 1..12, day: 1..31 }
         */
        const standardizeDate = function(date) { // ES6
            //--
            if (date instanceof Date) {
                let d = date;
                date = null;
                date = {
                    year: d.getFullYear(),
                    month: d.getMonth() + 1,
                    day: d.getDate()
                };
                d = null;
            } //end if
            //--
            return normalizeAndClone(date);
            //--
        }; //END
        _C$.standardizeDate = standardizeDate; // export


        /**
         * Get a Date Object as ISO
         *
         * @memberof smartJ$Date
         * @method getIsoDate
         * @static
         *
         * @param 	{Object} 	date 		The Raw Date Object as { year: YYYY, month: 1..12, day: 1..31 }
         * @param 	{Boolean} 	withTime 	If TRUE will use the date as provided, without any normalization and will return in addition: HH:ii:ss
         * @return 	{String} 				Normalized Date String as: YYYY-MM-DD or YYYY-MM-DD HH:ii:ss
         */
        const getIsoDate = function(date, withTime = false) { // ES6
            //--
            if (date instanceof Date) {
                let d = date;
                date = null;
                date = {
                    year: d.getFullYear(),
                    month: d.getMonth() + 1,
                    day: d.getDate()
                };
                if (withTime === true) {
                    date.hour = d.getHours() || 0;
                    date.minute = d.getMinutes() || 0;
                    date.second = d.getSeconds() || 0;
                } //end if
                d = null;
            } //end if
            //--
            if (withTime !== true) {
                date = normalizeAndClone(date);
            } //end if
            //--
            const add_d_m_LeadingZero = (d_m) => { // add leading zero to day or month: d_m is Integer
                if (d_m < 1) {
                    d_m = 1;
                } //end if
                if (d_m < 10) {
                    d_m = '0' + String(d_m);
                } //end if
                return String(d_m);
            }; //END
            const add_h_m_s_LeadingZero = (h_m_s) => { // add leading zero to hour, minute or second: h_m_s is Integer
                if (h_m_s < 0) {
                    h_m_s = 0;
                } //end if
                if (h_m_s < 10) {
                    h_m_s = '0' + String(h_m_s);
                } //end if
                return String(h_m_s);
            }; //END
            //--
            let y = String(date.year);
            let m = String(add_d_m_LeadingZero(date.month));
            let d = String(add_d_m_LeadingZero(date.day));
            //--
            if (withTime === true) {
                return String(y + '-' + m + '-' + d + ' ' + add_h_m_s_LeadingZero(date.hour || 0) + ':' + add_h_m_s_LeadingZero(date.minute || 0) + ':' + add_h_m_s_LeadingZero(date.second || 0));
            } else {
                return String(y + '-' + m + '-' + d);
            } //end if else
            //--
        }; //END
        _C$.getIsoDate = getIsoDate; // export


        /**
         * Calculate Days Offset between two dates
         *
         * @memberof smartJ$Date
         * @method calculateDaysOffset
         * @static
         *
         * @param 	{Object} 	sdate1 	Normalized Date #1 Object as: { year: YYYY, month: MM, day: DD }
         * @param 	{Object} 	sdate2 	Normalized Date #2 Object as: { year: YYYY, month: MM, day: DD }
         * @return 	{Integer} 			The Date Offset in seconds between sdate1 and sdate2 as: sdate2 - sdate1
         */
        const calculateDaysOffset = function(sdate1, sdate2) { // ES6
            //--
            let ofs1 = toOffset(sdate1.year, sdate1.month, sdate1.day);
            let ofs2 = toOffset(sdate2.year, sdate2.month, sdate2.day);
            //--
            return ofs2 - ofs1;
            //--
        }; //END
        _C$.calculateDaysOffset = calculateDaysOffset; // export


        /**
         * Calculate Months Offset between two dates
         *
         * @memberof smartJ$Date
         * @method calculateMonthsOffset
         * @static
         * @arrow
         *
         * @param 	{Object} 	sdate1 	Normalized Date #1 Object as: { year: YYYY, month: MM, day: DD }
         * @param 	{Object} 	sdate2 	Normalized Date #2 Object as: { year: YYYY, month: MM, day: DD }
         * @return 	{Integer} 			The Date Offset in seconds between sdate1 and sdate2 as: sdate2 - sdate1
         */
        const calculateMonthsOffset = (sdate1, sdate2) => ((sdate2.year - sdate1.year) * 12) + (sdate2.month - sdate1.month); //ES6
        _C$.calculateMonthsOffset = calculateMonthsOffset; // export


        /**
         * Add Years to a Date Object
         *
         * @memberof smartJ$Date
         * @method addYears
         * @static
         *
         * @param 	{Object} 	date 	The Raw Date Object as { year: YYYY, month: 1..12, day: 1..31 }
         * @param 	{Integer} 	years 	The number of Years to add or substract
         * @return 	{Object} 			Normalized Date Object as: { year: YYYY, month: MM, day: DD }
         */
        const addYears = function(date, years) { //ES6
            //--
            years = _Utils$.format_number_int(years);
            //--
            let sd = normalizeAndClone(date);
            sd.year += years;
            sd = clipDay(sd);
            //--
            return sd;
            //--
        }; //END
        _C$.addYears = addYears; // export


        /**
         * Add Months to a Date Object
         *
         * @memberof smartJ$Date
         * @method addMonths
         * @static
         *
         * @param 	{Object} 	date 	The Raw Date Object as { year: YYYY, month: 1..12, day: 1..31 }
         * @param 	{Integer} 	months 	The number Months to add or substract
         * @return 	{Object} 			Normalized Date Object as: { year: YYYY, month: MM, day: DD }
         */
        const addMonths = function(date, months) { // ES6
            //--
            months = _Utils$.format_number_int(months);
            //--
            let sd = normalizeAndClone(date);
            sd.month += months;
            //--
            const wrapMonth = function(date) { // wraps a month
                //--
                let yearOffset = yearOffsetForMonth(date.month);
                //--
                date.year += yearOffset;
                date.month -= yearOffset * 12;
                //--
                return date;
                //--
            };
            //--
            sd = wrapMonth(sd);
            sd = clipDay(sd);
            //--
            return sd;
            //--
        }; //END
        _C$.addMonths = addMonths; // export


        /**
         * Add Days to a Date Object
         *
         * @memberof smartJ$Date
         * @method addDays
         * @static
         *
         * @param 	{Object} 	date 	The Raw Date Object as { year: YYYY, month: 1..12, day: 1..31 }
         * @param 	{Integer} 	days 	The number Days to add or substract
         * @return 	{Object} 			Normalized Date Object as: { year: YYYY, month: MM, day: DD }
         */
        const addDays = function(date, days) { // ES6
            //--
            days = _Utils$.format_number_int(days);
            //--
            let normalized = normalizeAndClone(date);
            let offset = toOffset(normalized.year, normalized.month, normalized.day);
            //--
            return toDate(offset + days);
            //--
        }; //END
        _C$.addDays = addDays; // export


        /**
         * Test if the given Year is a Leap Year or not
         *
         * @memberof smartJ$Date
         * @method isLeapYear
         * @static
         *
         * @param 	{Integer} 	year 	The Year to be tested
         * @return 	{Boolean} 			TRUE if the Year is Leap or FALSE if is Not Leap
         */
        const isLeapYear = function(year) { // ES6
            //--
            year = _Utils$.format_number_int(year);
            //--
            let leap = true;
            if (year % 4 !== 0) {
                leap = false;
            } else if (year % 400 == 0) {
                leap = true;
            } else if (year % 100 == 0) {
                leap = false;
            } //end if else
            //--
            return leap;
            //--
        }; //END
        _C$.isLeapYear = isLeapYear; // export


        /**
         * Get the Number Of Days in a specific Month of the given Year
         *
         * @memberof smartJ$Date
         * @method daysInMonth
         * @static
         *
         * @param 	{Integer} 	year 	The Year to be tested
         * @param 	{Integer} 	month 	The Month to be tested
         * @return 	{Integer} 			the Number of Days in the tested month as: 28, 29, 30 or 31
         */
        const daysInMonth = function(year, month) { // ES6
            //--
            year = _Utils$.format_number_int(year);
            month = _Utils$.format_number_int(month);
            //--
            const DAYS_IN_MONTH = [
                31,
                28,
                31,
                30,
                31,
                30,
                31,
                31,
                30,
                31,
                30,
                31,
            ];
            //--
            let d = DAYS_IN_MONTH[month - 1];
            if (month === 2 && isLeapYear(year)) {
                d = 29;
            } //end if
            //--
            return d;
            //--
        }; //END
        _C$.daysInMonth = daysInMonth; // export


        /**
         * Format a date object into a string value.
         * The format can be combinations of the following:
         * d  - day of month (no leading zero) ;
         * dd - day of month (two digit) ;
         * o  - day of year (no leading zeros) ;
         * oo - day of year (three digit) ;
         * D  - day name short ;
         * DD - day name long ;
         * m  - month of year (no leading zero) ;
         * mm - month of year (two digit) ;
         * M  - month name short ;
         * MM - month name long ;
         * y  - year (two digit) ;
         * yy - year (four digit) ;
         * @ - Unix timestamp (ms since 01/01/1970) ;
         * ! - Windows ticks (100ns since 01/01/0001) ;
         * "..." - literal text ;
         * '' - single quote ;
         * @hint It is similar with jQueryUI formatDate
         *
         * @memberof smartJ$Date
         * @method formatDate
         * @static
         *
         * @param 	{String} 	format 			The desired format of the date
         * @param 	{Date} 		date 			The date value to format, from Date() object
         * @param 	{Object} 	settings 		Attributes include: dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) ; dayNames string[7] - names of the days from Sunday (optional) ; monthNamesShort string[12] - abbreviated names of the months (optional) ; monthNames string[12] - names of the months (optional)
         * @return 	{String} 					The date in the above format
         */
        const formatDate = function(format, date, settings) { // ES6
            //--
            // The function was taken from (c) jQueryUI/v1.12.0/2016-07-30 ; modified by unixman
            //--
            format = _Utils$.stringPureVal(format, true); // cast to string, trim
            if (format == '') {
                format = 'yy-mm-dd';
            } //end if
            //--
            if (!date) {
                return '';
            } //end if
            //--
            const defaultSettings = {
                monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], // Names of months for drop-down and formatting
                monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], // For formatting
                dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // For formatting
                dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] // For formatting
            };
            //--
            let iFormat,
                dayNamesShort = (settings ? settings.dayNamesShort : null) || defaultSettings.dayNamesShort,
                dayNames = (settings ? settings.dayNames : null) || defaultSettings.dayNames,
                monthNamesShort = (settings ? settings.monthNamesShort : null) || defaultSettings.monthNamesShort,
                monthNames = (settings ? settings.monthNames : null) || defaultSettings.monthNames,
                _ticksTo1970 = (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000), // ticks to 1970
                output = '',
                literal = false;
            //--
            const lookAhead = function(match) { // Check whether a format character is doubled
                let matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
                if (matches) {
                    iFormat++;
                } //end if
                return matches;
            };
            const formatNumber = function(match, value, len) { // Format a number, with leading zero if necessary
                let num = String(value);
                if (lookAhead(match)) {
                    while (num.length < len) {
                        num = '0' + num;
                    } //end while
                } //end if
                return num;
            };
            const formatName = (match, value, shortNames, longNames) => (lookAhead(match) ? longNames[value] : shortNames[value]); // Format a name, short or long as requested
            //--
            if (date) {
                for (iFormat = 0; iFormat < format.length; iFormat++) {
                    if (literal) {
                        if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
                            literal = false;
                        } else {
                            output += format.charAt(iFormat);
                        } //end if else
                    } else {
                        switch (format.charAt(iFormat)) {
                            case 'd':
                                output += formatNumber('d', date.getDate(), 2);
                                break;
                            case 'D':
                                output += formatName('D', date.getDay(), dayNamesShort, dayNames);
                                break;
                            case 'o':
                                output += formatNumber('o', Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3);
                                break;
                            case 'm':
                                output += formatNumber('m', date.getMonth() + 1, 2);
                                break;
                            case 'M':
                                output += formatName('M', date.getMonth(), monthNamesShort, monthNames);
                                break;
                            case 'y':
                                output += (lookAhead('y') ? date.getFullYear() : (date.getFullYear() % 100 < 10 ? '0' : '') + date.getFullYear() % 100);
                                break;
                            case '@':
                                output += date.getTime();
                                break;
                            case '!':
                                output += date.getTime() * 10000 + _ticksTo1970;
                                break;
                            case "'":
                                if (lookAhead("'")) {
                                    output += "'";
                                } else {
                                    literal = true;
                                } //end if else
                                break;
                            default:
                                output += format.charAt(iFormat);
                        } //end switch
                    } //end if else
                } //end for
            } //end if
            //--
            return String(output);
            //--
        }; //END
        _C$.formatDate = formatDate; // export


        /**
         * Determine a date by a Date object or Expression
         * Valid date objects or expressions:
         * new Date(1937, 1 - 1, 1) 	:: a date in the past, as object ;
         * '-1y -1m -1d' 				:: a date in the past as relative expression to the defaultDate ;
         * new Date(2037, 12 - 1, 31) 	:: a date in the future as object ;
         * '1y 1m 1d' 					:: a date in the future as relative expression to the defaultDate ;
         * @hint It is similar with jQueryUI determineDate
         *
         * @memberof smartJ$Date
         * @method determineDate
         * @static
         *
         * @param 	{Mixed} 	date 				The Date object or date relative expression to the defaultDate
         * @param 	{Mixed} 	defaultDate 		*Optional* null (for today) or a Date object / timestamp as default (selected) date to be used for relative expressions
         * @return 	{Mixed} 						A Date object or null if fails to validate expression
         */
        const determineDate = function(date, defaultDate) { // ES6
            //--
            // The function was taken from (c) jQueryUI/v1.12.0/2016-07-30 ; modified by unixman
            //--
            if ((defaultDate == undefined) || (defaultDate == 'undefined') || (defaultDate == '') || (!defaultDate)) { // undef tests also for null
                defaultDate = null; // fix by unixman
            } //end if
            //--
            const _daylightSavingAdjust = (date) => {
                if (!date) {
                    return null;
                } //end if
                date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
                return date;
            };
            //--
            const offsetNumeric = function(offset) {
                let date = new Date();
                date.setDate(date.getDate() + offset);
                return date;
            };
            //--
            const offsetString = function(offset) {
                let date = null;
                //if(offset.toLowerCase().match(/^c/)) {
                if (offset) { // fix by unixman
                    date = defaultDate;
                } //end if
                if (date == null) {
                    date = new Date();
                } //end if
                let year = date.getFullYear(),
                    month = date.getMonth(),
                    day = date.getDate();
                let pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,
                    matches = pattern.exec(offset);
                while (matches) {
                    switch (matches[2] || "d") {
                        case 'd':
                        case 'D':
                            day += parseInt(matches[1], 10);
                            break;
                        case 'w':
                        case 'W':
                            day += parseInt(matches[1], 10) * 7;
                            break;
                        case 'm':
                        case 'M':
                            month += parseInt(matches[1], 10);
                            day = Math.min(day, new Date(year, month + 1, 0).getDate()); // 2nd param is get days in month
                            break;
                        case 'y':
                        case 'Y':
                            year += parseInt(matches[1], 10);
                            day = Math.min(day, new Date(year, month + 1, 0).getDate()); // 2nd param is get days in month
                            break;
                    } //end switch
                    matches = pattern.exec(offset);
                } //end while
                return new Date(year, month, day);
            };
            //--
            let newDate = (date == null || date === '' ? defaultDate : (typeof(date) === 'string' ? offsetString(date) : (typeof(date) === 'number' ? (!_Utils$.isFiniteNumber(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime()))));
            newDate = (newDate && newDate.toString() === 'Invalid Date' ? defaultDate : newDate);
            if (newDate) {
                newDate.setHours(0);
                newDate.setMinutes(0);
                newDate.setSeconds(0);
                newDate.setMilliseconds(0);
            } //end if
            //--
            return _daylightSavingAdjust(newDate);
            //--
        }; //END
        _C$.determineDate = determineDate; // export


        /**
         * Convert the number of seconds (unixtime) into Pretty Format: Days, Hours, Minutes, Seconds
         *
         * @memberof smartJ$Date
         * @method prettySecondsToDHMS
         * @static
         *
         * @param 	{Integer} 	numSec 				The number of seconds to convert
         * @return 	{String} 						The pretty formated time as: '4 Days, 16 Hours, 28 Minutes, 7 Seconds'
         */
        const prettySecondsToDHMS = (numSec) => { // convert number of seconds (unix time) to pretty days, hours, minutes, seconds
            //--
            numSec = _Utils$.format_number_int(numSec, false);
            if (numSec <= 0) {
                return 'Now...';
            } //end if
            //--
            const days = Math.floor(numSec / (3600 * 24));
            const hours = Math.floor(numSec % (3600 * 24) / 3600);
            const minutes = Math.floor(numSec % 3600 / 60);
            const seconds = Math.floor(numSec % 60);
            //--
            let prettyFmt = '';
            if (days > 0) {
                prettyFmt += days + 'd ';
            } //end if
            if (hours > 0) {
                prettyFmt += hours + 'h ';
            } //end if
            if (minutes > 0) {
                prettyFmt += minutes + 'm ';
            } //end if
            prettyFmt += seconds + 's';
            //--
            return String(prettyFmt);
            //--
        };
        _C$.prettySecondsToDHMS = prettySecondsToDHMS; // export


        //===== PRIVATES


        // normalize a date
        const normalizeAndClone = function(date) { // ES6
            //--
            let yearOffset = yearOffsetForMonth(date.month);
            let year = date.year + yearOffset;
            let month = date.month - yearOffset * 12;
            //--
            return toDate(toOffset(year, month, date.day));
            //--
        }; //END


        // clips a day
        const clipDay = (date) => { // ES6
            //--
            date.day = Math.min(date.day, daysInMonth(date.year, date.month));
            //--
            return date;
            //--
        }; //END


        // get the Year offset for a specific Month
        const yearOffsetForMonth = function(month) { // ES6
            //--
            let ofs = 0;
            if (month > 12) {
                ofs = Math.ceil(month / 12) - 1;
            } else if (month < 1) {
                ofs = Math.floor((month - 1) / 12);
            } //end if else
            //--
            return ofs;
            //--
        }; //END


        // date-offset: calculate Y,M,D to Date
        const toDate = function(z) { // ES6
            //--
            z += 719468;
            //--
            let era = ((z >= 0 ? z : z - 146096) / 146097) | 0;
            let doe = z - era * 146097; // [0, 146096]
            let yoe = Math.floor((doe - Math.floor(doe / 1460) + Math.floor(doe / 36524) - Math.floor(doe / 146096)) / 365); // [0, 399]
            let y = yoe + era * 400;
            let doy = doe - (365 * yoe + Math.floor(yoe / 4) - Math.floor(yoe / 100)); // [0, 365]
            let mp = Math.floor((5 * doy + 2) / 153); // [0, 11]
            let d = doy - Math.floor((153 * mp + 2) / 5) + 1; // [1, 31]
            let m = mp + (mp < 10 ? 3 : -9); // [1, 12]
            //--
            return {
                year: y + (m <= 2),
                month: m,
                day: d
            };
            //--
        }; //END


        // date-offset: calculate Y,M,D to Offset
        const toOffset = function(y, m, d) { // ES6
            //--
            y -= m <= 2;
            //--
            let era = ((y >= 0 ? y : y - 399) / 400) | 0;
            let yoe = y - era * 400; // [0, 399]
            let doy = Math.floor((153 * (m + (m > 2 ? -3 : 9)) + 2) / 5) + d - 1; // [0, 365]
            let doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy; // [0, 146096]
            //--
            return era * 146097 + doe - 719468;
            //--
        }; //END


    }
}; //END CLASS

smartJ$Date.secureClass(); // implements class security

if (typeof(window) != 'undefined') {
    window.smartJ$Date = smartJ$Date; // global export
} //end if

//==================================================================
//==================================================================

// #END

// ===== crypt_utils.js

// [LIB - Smart.Framework / JS / Crypto Utils]
// (c) 2006-2022 unix-world.org - all rights reserved
// r.8.7 / smart.framework.v.8.7

// DEPENDS: smartJ$Utils
// CONTAINS: Base64 encode/decode ; Base32/36/58/62/64s/85/92 encode/decode ; CryptoHash: CRC32B, MD5, SHA1, SHA256, SHA512 (Hex / B64) ; Blowfish (CBC) encrypt / decrypt
// r.20220730

//==================================================================
// The code is released under the BSD License.
//  Copyright (c) unix-world.org
// The file contains portions of code from:
//	CRC32B: https://github.com/fastest963/js-crc32, Copyright (c) James Hartig
//	MD5, SHA1, SHA256, SHA512, Base64.Encode/Decode: https://github.com/hirak/phpjs, Copyright (c) Kevin van Zonneveld and Contributors (http://phpjs.org/authors)
//	Blowfish.Encrypt/Decrypt: https://github.com/agorlov/javascript-blowfish, Copyright (c) Alexandr Gorlov
//==================================================================

//================== [ES6]

//=======================================
// CLASS :: Crypto Test
//=======================================

/**
 * CLASS :: Smart TestCrypto (ES6, Strict Mode)
 *
 * @package Sf.Javascript:Crypto
 *
 * @private : used only internally
 *
 * @desc Test the browser compliance for the crypto classes
 * @author unix-world.org
 * @license BSD
 * @file crypt_utils.js
 * @version 20220730
 * @class smartJ$TestCrypto
 * @static
 * @frozen
 *
 */
const smartJ$TestCrypto = new class {
    constructor() { // STATIC CLASS (ES6)
        'use strict';
        const _N$ = 'smartJ$TestCrypto';

        // :: static
        const _C$ = this; // self referencing

        const _p$ = console;

        let SECURED = false;
        _C$.secureClass = () => { // implements class security
            if (SECURED === true) {
                _p$.warn(_N$, 'Class is already SECURED');
            } else {
                SECURED = true;
                Object.freeze(_C$);
            } //end if
        }; //END

        // :: static

        /**
         * Raise Error
         *
         * @memberof smartJ$TestCrypto
         * @method raiseError
         * @static
         *
         * @throws ERROR
         *
         * @param {String} err The Error Message
         */
        const raiseError = (err) => {
            _p$.error(_N$, 'ERR: Browser FAILED with:', err);
        }; //END
        _C$.raiseError = raiseError;

        //--
        // Base32  Test: 5ARJ9CDNM8P90ADQ74QBECST0I2E5JV2PTHD3OMHC90U4GB1QTGSEOEHC70M8J749HI4RP2D20A1G6KMJ0E93
        // Base36  Test: cksdjkz3988qjf4fcdy5bczersui2n235512z0czjcd5fwyk0ougp75sns9yckqgr67073sipmjr0n4nxf
        // Base58  Test: 2ZWiQiAbzWECL8Q7F297zGcNCeZHjccgwo7hw61WzGDuGErXR2ZaiB1Hc8AUuZEWTT4kj8xPL
        // Base62  Test: Nhp2biqj3pWbD2CRYvtc2AwDuJe44CJp8fRLSrw4x2bXrmDeGbOCW1kvxzasvrJXzJAmV4n
        // Base64s Test: VW5pY29kZSBTdHJpbmc6CQnFn8WexaPFosSDxILDrsOOw6LDgsiZyJjIm8iaICgwNS0wOSM.
        // Base85  Test: T@Df@<a).Oqo{b+!vomLhRM.OG^<(+?h!R?hqX)maSv9?C+/4[Ie2LRCx1S^Q6dQ+-
        // Base92  Test: u3LL}_`$ThRdx{k;ukc~B|0td8N~EuQoJa"SL/_c/@fWV>3)&b`+{+&ubLMikMcyH
        //--

        /**
         * Return Test expected result for Base64 algo for the string set in the class unicodeString()
         *
         * @memberof smartJ$TestCrypto
         * @method testBase64
         * @static
         */
        const testBase64 = () => {
            return 'VW5pY29kZSBTdHJpbmc6CQnFn8WexaPFosSDxILDrsOOw6LDgsiZyJjIm8iaICgwNS0wOSM=';
        }; //END
        _C$.testBase64 = testBase64; // export

        /**
         * Return Test expected result for CRC32B algo for the string set in the class unicodeString()
         *
         * @memberof smartJ$TestCrypto
         * @method testCRC32B
         * @static
         */
        const testCRC32B = (b36 = false) => {
            if (b36 === true) {
                return '01hcnnc';
            } else {
                return '055757b8';
            } //end if else
        }; //END
        _C$.testCRC32B = testCRC32B; // export

        /**
         * Return Test expected result for MD5 algo for the string set in the class unicodeString()
         *
         * @memberof smartJ$TestCrypto
         * @method testMD5
         * @static
         */
        const testMD5 = (b64 = false) => {
            if (b64 === true) {
                return 'PW7773idgEOjkmVB/rmhjA==';
            } else {
                return '3d6efbef789d8043a3926541feb9a18c';
            } //end if else
        }; //END
        _C$.testMD5 = testMD5; // export

        /**
         * Return Test expected result for SHA1 algo for the string set in the class unicodeString()
         *
         * @memberof smartJ$TestCrypto
         * @method testSHA1
         * @static
         */
        const testSHA1 = (b64 = false) => {
            if (b64 === true) {
                return 'WgoSffvbg0kbn7wO453JRwZsQmw=';
            } else {
                return '5a0a127dfbdb83491b9fbc0ee39dc947066c426c';
            } //end if else
        }; //END
        _C$.testSHA1 = testSHA1; // export

        /**
         * Return Test expected result for SHA256 algo for the string set in the class unicodeString()
         *
         * @memberof smartJ$TestCrypto
         * @method testSHA256
         * @static
         */
        const testSHA256 = (b64 = false) => {
            if (b64 === true) {
                return 'GTvIwHM9xKaYl7+tgXkV22cyONrQnWDMkSm7PMsiVKg=';
            } else {
                return '193bc8c0733dc4a69897bfad817915db673238dad09d60cc9129bb3ccb2254a8';
            } //end if else
        }; //END
        _C$.testSHA256 = testSHA256; // export

        /**
         * Return Test expected result for SHA512 algo for the string set in the class unicodeString()
         *
         * @memberof smartJ$TestCrypto
         * @method testSHA512
         * @static
         */
        const testSHA512 = (b64 = false) => {
            if (b64 === true) {
                return 'tRt7Uw8rciM8Vf4P08PES7q+TbboJq/Fi0hmilLqef5fvSclO5iu287kAdu4qgCpcFbdCIpWtEugozIN8ttcHg==';
            } else {
                return 'b51b7b530f2b72233c55fe0fd3c3c44bbabe4db6e826afc58b48668a52ea79fe5fbd27253b98aedbcee401dbb8aa00a97056dd088a56b44ba0a3320df2db5c1e';
            } //end if else
        }; //END
        _C$.testSHA512 = testSHA512; // export

        /**
         * Return Test expected result for a sample unicode string to test the algo's with
         *
         * @memberof smartJ$TestCrypto
         * @method unicodeString
         * @static
         */
        const unicodeString = () => {
            return 'Unicode String:		şŞţŢăĂîÎâÂșȘțȚ (05-09#';
        }; //END
        _C$.unicodeString = unicodeString; // export

    }
}; //END CLASS

smartJ$TestCrypto.secureClass(); // implements class security

if (typeof(window) != 'undefined') {
    window.smartJ$TestCrypto = smartJ$TestCrypto; // global export
} //end if

//=======================================
// CLASS :: Base64 enc/dec
//=======================================

/**
 * CLASS :: Smart Base64 (ES6, Strict Mode)
 *
 * @package Sf.Javascript:Crypto
 *
 * @requires		smartJ$Utils
 * @requires		smartJ$TestCrypto
 *
 * @throws 			console.error
 *
 * @desc Base64 for JavaScript: Encode / Decode
 * @author unix-world.org
 * @license BSD
 * @file crypt_utils.js
 * @version 20220730
 * @class smartJ$Base64
 * @static
 * @frozen
 *
 */
const smartJ$Base64 = new class {
    constructor() { // STATIC CLASS (ES6)
        'use strict';
        const _N$ = 'smartJ$Base64';

        // :: static
        const _C$ = this; // self referencing

        const _p$ = console;

        let SECURED = false;
        _C$.secureClass = () => { // implements class security
            if (SECURED === true) {
                _p$.warn(_N$, 'Class is already SECURED');
            } else {
                SECURED = true;
                Object.freeze(_C$);
            } //end if
        }; //END

        const _Utils$ = smartJ$Utils;

        const _Test$ = smartJ$TestCrypto;

        //== PRIVATES BASE64

        let testBase64PassedEnc = false;
        let testBase64PassedDec = false;

        //== PUBLIC BASE64

        /**
         * Encode a string to Base64
         * Supports also UTF-8
         *
         * @hint Javascript btoa() does not support UTF-8 but only ASCII
         *
         * @memberof smartJ$Base64
         * @method encode
         * @static
         *
         * @throws console.error
         *
         * @param {String} s The plain string (to be encoded)
         * @param {Boolean} bin Set to TRUE if the string is binary to avoid re-encode to UTF-8
         * @return {String} the Base64 encoded string
         */
        const encode = function(s, bin = false) {
            //--
            if (testBase64PassedEnc !== true) {
                if (base64_core_enc(_Test$.unicodeString()) == _Test$.testBase64()) {
                    testBase64PassedEnc = true; // test passed
                } else { // test failed
                    _Test$.raiseError('Base64/Encode');
                    return '';
                } //end if else
            } //end if
            //--
            return String(base64_core_enc(s, bin));
            //--
        }; //END
        _C$.encode = encode;

        /**
         * Decode a string from Base64
         * Supports also UTF-8
         *
         * @hint Javascript atob() does not support UTF-8 but only ASCII
         *
         * @memberof smartJ$Base64
         * @method decode
         * @static
         *
         * @throws console.error
         *
         * @param {String} s The B64 encoded string
         * @param {Boolean} bin Set to TRUE if the string is binary to avoid re-decode as UTF-8
         * @return {String} the plain (B64 decoded) string
         */
        const decode = function(s, bin = false) {
            //--
            if (testBase64PassedDec !== true) {
                if (base64_core_dec(_Test$.testBase64()) == _Test$.unicodeString()) {
                    testBase64PassedDec = true; // test passed
                } else { // test failed
                    _Test$.raiseError('Base64/Decode');
                    return '';
                } //end if else
            } //end if
            //--
            return String(base64_core_dec(s, bin));
            //--
        }; //END
        _C$.decode = decode;

        //== PRIVATES BASE64

        // PRIVATE :: BASE64 :: key
        const b64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';

        // PRIVATE :: BASE64 :: encode
        const base64_core_enc = function(input, is_binary) {
            //-- safety checks
            input = _Utils$.stringPureVal(input); // cast to string, don't trim ! need to preserve the value
            if (input == '') {
                return '';
            } //end if
            //-- make it unicode
            if (is_binary !== true) { // binary content must not be re-encoded to UTF-8
                input = _Utils$.utf8_encode(input);
            } //end if
            //-- keys
            const keyStr = String(b64Chars);
            //-- encoder
            let output = '';
            let chr1, chr2, chr3;
            let enc1, enc2, enc3, enc4;
            let i = 0;
            //--
            do {
                //--
                chr1 = input.charCodeAt(i++);
                chr2 = input.charCodeAt(i++);
                chr3 = input.charCodeAt(i++);
                //--
                enc1 = chr1 >> 2;
                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                enc4 = chr3 & 63;
                //--
                if (isNaN(chr2)) {
                    enc3 = enc4 = 64;
                } else if (isNaN(chr3)) {
                    enc4 = 64;
                } //end if
                //--
                output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) +
                    keyStr.charAt(enc3) + keyStr.charAt(enc4);
                //--
            } while (i < input.length);
            //--
            return String(output);
            //--
        }; //END

        // PRIVATE :: BASE64 :: decode
        const base64_core_dec = function(input, is_binary) {
            //-- safety checks
            input = _Utils$.stringPureVal(input); // cast to string, don't trim ! need to preserve the value
            if (input == '') {
                return '';
            } //end if
            //-- keys
            const keyStr = String(b64Chars);
            //-- decoder
            let output = '';
            let chr1, chr2, chr3;
            let enc1, enc2, enc3, enc4;
            let i = 0;
            //--
            input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
            //--
            do {
                //--
                enc1 = keyStr.indexOf(input.charAt(i++));
                enc2 = keyStr.indexOf(input.charAt(i++));
                enc3 = keyStr.indexOf(input.charAt(i++));
                enc4 = keyStr.indexOf(input.charAt(i++));
                //--
                chr1 = (enc1 << 2) | (enc2 >> 4);
                chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                chr3 = ((enc3 & 3) << 6) | enc4;
                //--
                output = output + String.fromCharCode(chr1);
                //--
                if (enc3 != 64) {
                    output = output + String.fromCharCode(chr2);
                } //end if
                //--
                if (enc4 != 64) {
                    output = output + String.fromCharCode(chr3);
                } //end if
                //--
            } while (i < input.length);
            //--
            if (is_binary !== true) { // binary content must not be re-decoded as UTF-8
                output = _Utils$.utf8_decode(output); // make it back unicode safe
            } //end if
            //--
            return String(output);
            //--
        }; //END

    }
}; //END CLASS

smartJ$Base64.secureClass(); // implements class security

if (typeof(window) != 'undefined') {
    window.smartJ$Base64 = smartJ$Base64; // global export
} //end if

//=======================================
// CLASS :: Base Convert enc/dec
//=======================================

/**
 * CLASS :: Smart Base Convert (ES6, Strict Mode)
 *
 * @package Sf.Javascript:Crypto
 *
 * @requires		smartJ$Utils
 * @requires		smartJ$TestCrypto
 *
 * @throws 			console.error
 *
 * @desc Base Convert for JavaScript: Encode / Decode: base32, base36, base58, base62, base64s, base85, base92
 * @author unix-world.org
 * @license BSD
 * @file crypt_utils.js
 * @version 20220730
 * @class smartJ$BaseEncode
 * @static
 * @frozen
 *
 */
const smartJ$BaseEncode = new class {
    constructor() { // STATIC CLASS (ES6)
        'use strict';
        const _N$ = 'smartJ$BaseEncode';

        // :: static
        const _C$ = this; // self referencing

        const _p$ = console;

        let SECURED = false;
        _C$.secureClass = () => { // implements class security
            if (SECURED === true) {
                _p$.warn(_N$, 'Class is already SECURED');
            } else {
                SECURED = true;
                Object.freeze(_C$);
            } //end if
        }; //END

        const _Utils$ = smartJ$Utils;

        const _Ba$e64 = smartJ$Base64;

        /*
         * Returns the Base64 (Safe URL) Modified Encoding from a string by replacing the standard base64 encoding as follows:
         * '+' with '-',
         * '/' with '_',
         * '=' with '.'
         *
         * @private internal use only
         *
         * @memberof smartJ$BaseEncode
         * @method b64s_enc
         * @static
         *
         * @param {String} 	str 				The string to be encoded
         * @param {Boolean} bin Set to TRUE if the string is binary to avoid re-encode to UTF-8
         * @return {String} 					The safe URL Base64 encoded string
         */
        const b64s_enc = function(str, bin = false) {
            //--
            return _Ba$e64.encode(str, bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '.');
            //--
        }; //END
        _C$.b64s_enc = b64s_enc; // hidden export

        /*
         * Returns the Decoded string from the Base64 (Safe URL) Encoding by replacing back as follows before applying the standard base64 decoding:
         * '-' with '+',
         * '_' with '/',
         * '.' with '='
         *
         * @private internal use only
         *
         * @memberof smartJ$BaseEncode
         * @method b64s_dec
         * @static
         *
         * @param STRING 	enc 				The safe URL Base64 encoded string
         * @param {Boolean} bin 				Set to TRUE if the string is binary to avoid re-decode as UTF-8
         * @return STRING 						The decoded string
         *
         */
        const b64s_dec = function(enc, bin = false) {
            //--
            return _Ba$e64.decode(_Utils$.stringPureVal(enc).replace(/\-/g, '+').replace(/_/g, '/').replace(/\./g, '='), bin);
            //--
        }; //END
        _C$.b64s_dec = b64s_dec; // hidden export

        /**
         * Safe convert to hex from any of the following bases: 32, 36, 58, 62, 85, 92
         * In case of error will return an empty string.
         *
         * @memberof smartJ$Utils
         * @method base_to_hex_convert
         * @static
         *
         * @param  {String} 	encoded			:: A string (baseXX encoded) that was previous encoded using base_from_hex_convert()
         * @param  {Integer} 	currentBase		:: The base to convert ; Available source base: 32, 36, 58, 62, 85, 92
         * @return {String} 					:: The encoded string in the selected base or empty string on error
         */
        const base_to_hex_convert = function(encoded, currentBase) {
            //--
            // based on idea by: https://github.com/tuupola/base62 # License MIT
            //--
            const _m$ = 'base_to_hex_convert';
            //--
            encoded = _Utils$.stringPureVal(encoded, true);
            if (encoded == '') {
                _p$.warn(_N$, _m$, 'Empty Input');
                return '';
            } //end if
            //--
            let baseCharset = base_get_alphabet(currentBase);
            if (baseCharset == '') {
                _p$.warn(_N$, _m$, 'Invalid Current Base:', currentBase);
                return '';
            } //end if
            currentBase = baseCharset.length;
            //--
            let data = encoded.split('');
            encoded = null;
            data = data.map((c) => {
                const result = String(baseCharset).indexOf(c);
                if (result < 0) {
                    _p$.warn(_N$, _m$, 'Invalid Base Character:', c);
                }
                return result;
            });
            //--
            let leadingZeroes = 0;
            while (data.length && 0 === data[0]) {
                leadingZeroes++;
                data.shift(); // trim off leading zeroes
            } //end while
            //--
            let converted = base_asciihex_convert(data, currentBase, 256);
            data = null;
            //--
            if (0 < leadingZeroes) {
                let arrZeroFill = new Array(leadingZeroes).fill(0, 0, leadingZeroes);
                converted = [].concat(arrZeroFill, converted);
            } //end if
            //--
            converted = converted.map((c) => String.fromCharCode(c)).join(''); // map ascii (256) to binary ; [php] chr($code) = [js] String.fromCharCode(code)
            //--
            return String(_Utils$.bin2hex(converted, true));
            //--
        }; //END
        _C$.base_to_hex_convert = base_to_hex_convert; // export

        /**
         * Safe convert from hex to any of the following bases: 32, 36, 58, 62, 85, 92
         * In case of error will return an empty string.
         *
         * @memberof smartJ$Utils
         * @method base_from_hex_convert
         * @static
         *
         * @param {String} 		hexstr			:: A hexadecimal string (base16) ; can be from bin2hex(string) or from dechex(integer) but in the case of using dechex must use also left padding with zeros to have an even length of the hex data
         * @param {Integer} 	targetBase		:: The base to convert ; Available target base: 32, 36, 58, 62, 85, 92
         * @return {String} 					:: The encoded string in the selected base or empty string on error
         */
        const base_from_hex_convert = function(hexstr, targetBase) {
            //--
            // based on idea by: https://github.com/tuupola/base62 # License MIT
            //--
            const _m$ = 'base_from_hex_convert';
            //--
            hexstr = _Utils$.stringPureVal(hexstr, true);
            if (hexstr == '') {
                _p$.warn(_N$, _m$, 'Empty Input');
                return '';
            } //end if
            //--
            let baseCharset = base_get_alphabet(targetBase);
            if (baseCharset == '') {
                _p$.warn(_N$, _m$, 'Invalid Target Base:', targetBase);
                return '';
            } //end if
            targetBase = baseCharset.length;
            //--
            let source = String(_Utils$.hex2bin(hexstr, true)); // lowercase will apply in hex2bin
            if (source == '') {
                _p$.warn(_N$, _m$, 'Invalid Input, NOT HEX:', hexstr);
                return '';
            } //end if
            hexstr = null; // free mem
            source = source.split('');
            source = source.map((c) => String(c).charCodeAt(0)); // map hex (16) to ascii (256) ; [php] ord($str) = [js] str.charCodeAt(0)
            //--
            let leadingZeroes = 0;
            while (source.length && 0 === source[0]) {
                leadingZeroes++;
                source.shift(); // trim off leading zeroes
            } //end while
            //
            //--
            let result = base_asciihex_convert(source, 256, targetBase);
            source = null;
            //--
            if (0 < leadingZeroes) {
                let arrZeroFill = new Array(leadingZeroes).fill(0, 0, leadingZeroes);
                result = [].concat(arrZeroFill, result);
            } //end if
            //--
            baseCharset = baseCharset.split('');
            //--
            return String(result.map((el) => baseCharset[el]).join(''));
            //--
        }; //END
        _C$.base_from_hex_convert = base_from_hex_convert; // export

        /*
         * Get the alphabet for base conversions
         *
         * @private no export
         *
         * @noexport
         * @static
         *
         * @param 	{String} 	theBase 		The base: '32', '36', '58', '62', '85', '92'
         * @return 	{String} 					The base alphabet or empty string if invalid base
         */
        const base_get_alphabet = function(theBase) {
            //--
            const minAlphabet = '0123456789abcdefghijklmnopqrstuv'; // b32
            const minComplAlphabet = 'wxyz'; // b36 extra with b32
            const extraAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; // b62
            const glyphAlphabet = '.-:+=^!/*?&<>()[]{}@%$#'; // b85
            const glyphExtAlphabet = '|;,_~`"'; // b92 extra with b85
            const altAlphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; // b58
            //--
            const targetBase = _Utils$.stringPureVal(theBase, true);
            let fromcharset = '';
            switch (targetBase) {
                case '32':
                    fromcharset = String(minAlphabet).toUpperCase();
                    break;
                case '36':
                    fromcharset = String(minAlphabet) + String(minComplAlphabet);
                    break;
                case '58':
                    fromcharset = String(altAlphabet); // compatible with smartgo
                    break;
                case '62':
                    fromcharset = String(minAlphabet) + String(minComplAlphabet) + String(extraAlphabet);
                    break;
                case '85':
                    fromcharset = String(minAlphabet) + String(minComplAlphabet) + String(extraAlphabet) + String(glyphAlphabet); // https://rfc.zeromq.org/spec:32/Z85/
                    break;
                case '92':
                    fromcharset = String(minAlphabet) + String(minComplAlphabet) + String(extraAlphabet) + String(glyphAlphabet) + String(glyphExtAlphabet); // uxm, compatible with smartgo
                    break;
                default:
                    _p$.error(_N$, 'base_get_alphabet', 'Invalid Base:', targetBase);
            } //end switch
            //--
            return String(fromcharset);
            //--
        }; //END
        // no export

        /*
         * Convert between bases: 16 (hex) to any of: 32, 36, 58, 62, 85, 92 | or viceversa
         *
         * @private no export
         *
         * @noexport
         * @static
         *
         * @param 	{Array} 	source
         * @param 	{Integer} 	sourceBase 		the source base to convert from
         * @param 	{Integer} 	targetBase 		the target base to convert to
         * @return 	{Array} 					result map for conversions
         */
        const base_asciihex_convert = function(source, sourceBase, targetBase) {
            //--
            // based on idea by: https://github.com/tuupola/base62 # License MIT
            //--
            let result = [];
            let count;
            while (count = source.length) {
                let quotient = [];
                let remainder = 0;
                for (let i = 0; i !== count; i++) {
                    let accumulator = source[i] + remainder * sourceBase;
                    let digit = (accumulator - (accumulator % targetBase)) / targetBase;
                    remainder = accumulator % targetBase;
                    if (quotient.length || digit) {
                        quotient.push(digit);
                    } //end if
                } //end for
                result.unshift(remainder);
                source = quotient;
            } //end while
            //--
            return result;
            //--
        }; //END
        // no export

    }
}; //END CLASS

smartJ$BaseEncode.secureClass(); // implements class security

if (typeof(window) != 'undefined') {
    window.smartJ$BaseEncode = smartJ$BaseEncode; // global export
} //end if

//=======================================
// CLASS :: Hash Crypto
//=======================================

/**
 * CLASS :: Smart CryptoHash (ES6, Strict Mode)
 *
 * @package Sf.Javascript:Crypto
 *
 * @requires		smartJ$Utils
 * @requires		smartJ$TestCrypto
 *
 * @throws 			smartJ$TestCrypto.raiseError
 *
 * @desc Crypto Hash for JavaScript: CRC32B / MD5 / SHA1 / SHA256 / SHA512 :: (Hex / B64)
 * @author unix-world.org
 * @license BSD
 * @file crypt_utils.js
 * @version 20220730
 * @class smartJ$CryptoHash
 * @static
 * @frozen
 *
 */
const smartJ$CryptoHash = new class {
    constructor() { // STATIC CLASS (ES6)
        'use strict';
        const _N$ = 'smartJ$CryptoHash';

        // :: static
        const _C$ = this; // self referencing

        const _p$ = console;

        let SECURED = false;
        _C$.secureClass = () => { // implements class security
            if (SECURED === true) {
                _p$.warn(_N$, 'Class is already SECURED');
            } else {
                SECURED = true;
                Object.freeze(_C$);
            } //end if
        }; //END

        const _Utils$ = smartJ$Utils;
        const _Ba$e64 = smartJ$Base64;

        const _Test$ = smartJ$TestCrypto;

        //== PRIVATES CRYPTO

        let testCryptoHash_hex_CRC32B = false;
        let testCryptoHash_b36_CRC32B = false;

        let testCryptoHash_hex_MD5 = false;
        let testCryptoHash_b64_MD5 = false;

        let testCryptoHash_hex_SHA1 = false;
        let testCryptoHash_b64_SHA1 = false;

        let testCryptoHash_hex_SHA256 = false;
        let testCryptoHash_b64_SHA256 = false;

        let testCryptoHash_hex_SHA512 = false;
        let testCryptoHash_b64_SHA512 = false;

        //	const listHexaLower = '0123456789abcdef';

        // PRIVATE :: CRYPTO :: the method acts as a setting for chrsz
        const crypt_chrsz = () => {
            return 8; // bits per input character. 8 - ASCII; 16 - Unicode (sync with the other methods)
        }; //END

        // PRIVATE :: CRYPTO :: Add integers, wrapping at 2^32. It uses 16-bit operations internally to work around bugs in some JS interpreters.
        const crypt_safe_add = function(x, y) {
            let lsw = (x & 0xFFFF) + (y & 0xFFFF);
            let msw = (x >> 16) + (y >> 16) + (lsw >> 16);
            return (msw << 16) | (lsw & 0xFFFF);
        }; //END

        // PRIVATE :: CRYPTO :: Bitwise rotate a 32-bit number to the left.
        const crypt_bit_rol = (num, cnt) => {
            return (num << cnt) | (num >>> (32 - cnt));
        }; //END

        // PRIVATE :: CRYPTO :: Convert a string to an Array of little-endian words. If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
        const crypt_str2binl = function(str) {
            let bin = Array();
            let mask = (1 << crypt_chrsz()) - 1;
            for (let i = 0; i < str.length * crypt_chrsz(); i += crypt_chrsz()) {
                bin[i >> 5] |= (str.charCodeAt(i / crypt_chrsz()) & mask) << (i % 32);
            } //end for
            return bin;
        }; //END

        // PRIVATE :: CRYPTO :: Convert an Array of little-endian words to a string
        const crypt_binl2str = function(bin) {
            let str = '';
            let mask = (1 << crypt_chrsz()) - 1;
            for (let i = 0; i < bin.length * 32; i += crypt_chrsz()) {
                str += String.fromCharCode((bin[i >> 5] >>> (i % 32)) & mask);
            } //end for
            return str;
        }; //END

        /*
        // PRIVATE :: CRYPTO :: Convert an Array of little-endian words to a hex string.
        const crypt_binl2hex = function(binarray) {
        	const hex_tab = listHexaLower;
        	let str = '';
        	for(let i = 0; i < binarray.length * 4; i++) {
        		str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8)) & 0xF);
        	} //end for
        	return str;
        }; //END
        */

        // PRIVATE :: CRYPTO :: Convert an 8-bit or 16-bit string to an Array of big-endian words
        const crypt_str2binb = function(str) {
            const mask = (1 << crypt_chrsz()) - 1;
            let bin = Array();
            for (let i = 0; i < str.length * crypt_chrsz(); i += crypt_chrsz()) {
                bin[i >> 5] |= (str.charCodeAt(i / crypt_chrsz()) & mask) << (32 - crypt_chrsz() - i % 32);
            } //end for
            return bin;
        }; //END

        // PRIVATE :: CRYPTO :: Convert an Array of big-endian words to a string
        const crypt_binb2str = function(bin) {
            const mask = (1 << crypt_chrsz()) - 1;
            let str = '';
            for (let i = 0; i < bin.length * 32; i += crypt_chrsz()) {
                str += String.fromCharCode((bin[i >> 5] >>> (32 - crypt_chrsz() - i % 32)) & mask);
            } //end for
            return str;
        }; //END

        /*
        // PRIVATE :: CRYPTO :: Convert an Array of big-endian words to a hex string.
        const crypt_binb2hex = function(binarray) {
        	const hex_tab = listHexaLower;
        	let str = '';
        	for(let i = 0; i < binarray.length * 4; i++) {
        		str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8)) & 0xF);
        	} //end for
        	return str;
        }; //END
        */

        // PRIVATE :: CRYPTO :: Convert a String to Array.
        const crypt_strtoarr = function(str) {
            const l = str.length;
            let bytes = new Array(l);
            for (let i = 0; i < l; i++) {
                bytes[i] = str.charCodeAt(i);
            } //end for
            return bytes;
        }; //END

        //== PUBLIC CRC32B

        /**
         * Returns the CRC32B hash of a string
         *
         * @memberof smartJ$CryptoHash
         * @method crc32b
         * @static
         *
         * @throws console.error
         *
         * @param {String} s The string
         * @param {Boolean} b36 If set to TRUE will use Base36 Encoding instead of Hex Encoding
         * @return {String} The CRC32B hash of the string
         */
        const crc32b = function(s, b36 = false) {
            //--
            if (b36 === true) {
                //--
                if (testCryptoHash_b36_CRC32B !== true) { // run test
                    if (crc32b_b36(_Test$.unicodeString()) == _Test$.testCRC32B(true)) {
                        testCryptoHash_b36_CRC32B = true; // test passed
                    } else { // test failed
                        _Test$.raiseError('CRC32B/Hash/B36');
                        return '';
                    } //end if else
                } //end if
                //--
                return String(crc32b_b36(s));
                //--
            } else {
                //--
                if (testCryptoHash_hex_CRC32B !== true) { // run test
                    if (crc32b_hex(_Test$.unicodeString()) == _Test$.testCRC32B()) {
                        testCryptoHash_hex_CRC32B = true; // test passed
                    } else { // test failed
                        _Test$.raiseError('CRC32B/Hash/Hex');
                        return '';
                    } //end if else
                } //end if
                //--
                return String(crc32b_hex(s));
                //--
            } //end if else
            //--
        }; //END
        _C$.crc32b = crc32b;

        //== PRIVATES CRC32

        let CRC32B_TABLE = null;
        const crc32b_init_tbl = function() {
            if (CRC32B_TABLE !== null) {
                return;
            } //end if
            CRC32B_TABLE = new Array(256);
            let i = 0,
                c = 0,
                b = 0;
            for (i = 0; i < 256; i++) {
                c = i;
                b = 8;
                while (b--) {
                    c = (c >>> 1) ^ ((c & 1) ? 0xEDB88320 : 0);
                } //end while
                CRC32B_TABLE[i] = c;
            } //end for
        }; //END

        const crc32b_hex = function(s) {
            s = _Utils$.stringPureVal(s); // cast to string, don't trim ! need to preserve the value
            s = _Utils$.utf8_encode(s); // make it unicode
            let crc = String(crc32b_core(s).toString(16)); // hex
            //-- unixman fix (pad with leading zeroes)
            const padding = 8 - (crc.length);
            if (padding > 0) {
                for (let i = 0; i < padding; i++) {
                    crc = '0' + crc;
                } //end for
            } //end if
            //--
            return String(crc);
        }; //END

        const crc32b_b36 = function(s) {
            s = _Utils$.stringPureVal(s); // cast to string, don't trim ! need to preserve the value
            s = _Utils$.utf8_encode(s); // make it unicode
            let crc = String(crc32b_core(s).toString(36)); // b36
            //-- unixman fix (pad with leading zeroes)
            const padding = 7 - (crc.length);
            if (padding > 0) {
                for (let i = 0; i < padding; i++) {
                    crc = '0' + crc;
                } //end for
            } //end if
            //--
            return String(crc);
        }; //END

        const crc32b_core = function(s) {
            //--
            let values = crypt_strtoarr(s),
                crc = -1,
                i = 0,
                l = values.length,
                isObjects = (typeof(values[0]) === 'object'),
                id = 0;
            crc32b_init_tbl();
            for (i = 0; i < l; i++) {
                id = isObjects ? (values[i].id >>> 0) : values[i];
                crc = CRC32B_TABLE[(crc ^ id) & 0xFF] ^ (crc >>> 8);
            } //end for
            crc = (~crc >>> 0); // bitflip then cast to 32-bit unsigned
            //--
            return crc;
            //--
        }; //END

        //== PRIVATES MD5

        // PRIVATE :: MD5 :: encode (hex)
        const md5_hex = (s) => {
            s = _Utils$.stringPureVal(s); // cast to string, don't trim ! need to preserve the value
            s = _Utils$.utf8_encode(s); // make it unicode
            //	return crypt_binl2hex(md5_core(crypt_str2binl(s), s.length * crypt_chrsz()));
            return _Utils$.bin2hex(crypt_binl2str(md5_core(crypt_str2binl(s), s.length * crypt_chrsz())), true);
        }; //END

        // PRIVATE :: MD5 :: encode (b64)
        const md5_b64 = (s) => {
            s = _Utils$.stringPureVal(s); // cast to string, don't trim ! need to preserve the value
            s = _Utils$.utf8_encode(s); // make it unicode
            return _Ba$e64.encode(crypt_binl2str(md5_core(crypt_str2binl(s), s.length * crypt_chrsz())), true);
        }; //END

        // PRIVATE :: MD5 :: basic operation 0 for the md5 algorithm
        const md5_cmn = (q, a, b, x, s, t) => {
            return crypt_safe_add(crypt_bit_rol(crypt_safe_add(crypt_safe_add(a, q), crypt_safe_add(x, t)), s), b);
        }; //END

        // PRIVATE :: MD5 :: basic operation 1 for the md5 algorithm
        const md5_ff = (a, b, c, d, x, s, t) => {
            return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
        }; //END

        // PRIVATE :: MD5 :: basic operation 2 for the md5 algorithm
        const md5_gg = (a, b, c, d, x, s, t) => {
            return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
        }; //END

        // PRIVATE :: MD5 :: basic operation 3 for the md5 algorithm
        const md5_hh = (a, b, c, d, x, s, t) => {
            return md5_cmn(b ^ c ^ d, a, b, x, s, t);
        }; //END

        // PRIVATE :: MD5 :: basic operation 4 for the md5 algorithm
        const md5_ii = (a, b, c, d, x, s, t) => {
            return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
        }; //END

        // PRIVATE :: MD5 :: core
        const md5_core = function(x, len) {
            //-- append padding
            x[len >> 5] |= 0x80 << ((len) % 32);
            x[(((len + 64) >>> 9) << 4) + 14] = len;
            //--
            let a = 1732584193;
            let b = -271733879;
            let c = -1732584194;
            let d = 271733878;
            //--
            for (let i = 0; i < x.length; i += 16) {
                //--
                let olda = a;
                let oldb = b;
                let oldc = c;
                let oldd = d;
                //--
                a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);
                d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
                c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
                b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
                a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
                d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
                c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
                b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
                a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
                d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
                c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
                b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
                a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
                d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
                c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
                b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
                //--
                a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
                d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
                c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
                b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);
                a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
                d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
                c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
                b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
                a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
                d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
                c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
                b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
                a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
                d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
                c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
                b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
                //--
                a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
                d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
                c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
                b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
                a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
                d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
                c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
                b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
                a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
                d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);
                c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
                b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
                a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
                d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
                c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
                b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
                //--
                a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);
                d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
                c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
                b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
                a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
                d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
                c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
                b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
                a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
                d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
                c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
                b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
                a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
                d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
                c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
                b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
                //--
                a = crypt_safe_add(a, olda);
                b = crypt_safe_add(b, oldb);
                c = crypt_safe_add(c, oldc);
                d = crypt_safe_add(d, oldd);
                //--
            } //end for
            //--
            return Array(a, b, c, d);
            //--
        }; //END

        //== PUBLIC MD5

        /**
         * Returns the MD5 hash of a string
         *
         * @memberof smartJ$CryptoHash
         * @method md5
         * @static
         *
         * @throws console.error
         *
         * @param {String} s The string
         * @param {Boolean} b64 If set to TRUE will use Base64 Encoding instead of Hex Encoding
         * @return {String} The MD5 hash of the string
         */
        const md5 = function(s, b64 = false) {
            //--
            if (b64 === true) {
                //--
                if (testCryptoHash_b64_MD5 !== true) { // run test
                    if (md5_b64(_Test$.unicodeString()) == _Test$.testMD5(true)) {
                        testCryptoHash_b64_MD5 = true; // test passed
                    } else { // test failed
                        _Test$.raiseError('MD5/Hash/B64');
                        return '';
                    } //end if else
                } //end if
                //--
                return String(md5_b64(s));
                //--
            } else {
                //--
                if (testCryptoHash_hex_MD5 !== true) { // run test
                    if (md5_hex(_Test$.unicodeString()) == _Test$.testMD5()) {
                        testCryptoHash_hex_MD5 = true; // test passed
                    } else { // test failed
                        _Test$.raiseError('MD5/Hash/Hex');
                        return '';
                    } //end if else
                } //end if
                //--
                return String(md5_hex(s));
                //--
            } //end if else
            //--
        }; //END
        _C$.md5 = md5; // export

        //== PRIVATES SHA1

        // PRIVATE :: SHA1 :: encrypt (hex)
        const sha1_hex = (s) => {
            s = _Utils$.stringPureVal(s); // cast to string, don't trim ! need to preserve the value
            s = _Utils$.utf8_encode(s); // make it unicode
            //	return crypt_binb2hex(sha1_core(crypt_str2binb(s), s.length * crypt_chrsz()));
            return _Utils$.bin2hex(crypt_binb2str(sha1_core(crypt_str2binb(s), s.length * crypt_chrsz())), true);
        }; //END

        // PRIVATE :: SHA1 :: encrypt (b64)
        const sha1_b64 = (s) => {
            s = _Utils$.stringPureVal(s); // cast to string, don't trim ! need to preserve the value
            s = _Utils$.utf8_encode(s); // make it unicode
            return _Ba$e64.encode(crypt_binb2str(sha1_core(crypt_str2binb(s), s.length * crypt_chrsz())), true);
        }; //END

        // PRIVATE :: SHA1 :: basic operation 0 for the sha1 algorithm
        const sha1_ft = (t, b, c, d) => {
            if (t < 20) {
                return (b & c) | ((~b) & d);
            } //end if
            if (t < 40) {
                return b ^ c ^ d;
            } //end if
            if (t < 60) {
                return (b & c) | (b & d) | (c & d);
            } //end if
            return b ^ c ^ d;
        }; //END

        // PRIVATE :: SHA1 :: basic operation 1 for the sha1 algorithm
        const sha1_kt = (t) => {
            return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514;
        }; //END

        // PRIVATE :: SHA1 :: core
        const sha1_core = function(x, len) {
            //-- append padding
            x[len >> 5] |= 0x80 << (24 - len % 32);
            x[((len + 64 >> 9) << 4) + 15] = len;
            //--
            let a = 1732584193;
            let b = -271733879;
            let c = -1732584194;
            let d = 271733878;
            let e = -1009589776;
            //--
            let w = Array(80);
            //--
            for (let i = 0; i < x.length; i += 16) {
                //--
                let olda = a;
                let oldb = b;
                let oldc = c;
                let oldd = d;
                let olde = e;
                //--
                for (let j = 0; j < 80; j++) {
                    if (j < 16) {
                        w[j] = x[i + j];
                    } else {
                        w[j] = crypt_bit_rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
                    } //end if else
                    //--
                    let t = crypt_safe_add(crypt_safe_add(crypt_bit_rol(a, 5), sha1_ft(j, b, c, d)), crypt_safe_add(crypt_safe_add(e, w[j]), sha1_kt(j)));
                    //--
                    e = d;
                    d = c;
                    c = crypt_bit_rol(b, 30);
                    b = a;
                    a = t;
                    //--
                } //end for
                //--
                a = crypt_safe_add(a, olda);
                b = crypt_safe_add(b, oldb);
                c = crypt_safe_add(c, oldc);
                d = crypt_safe_add(d, oldd);
                e = crypt_safe_add(e, olde);
            } //end for
            //--
            return Array(a, b, c, d, e);
            //--
        }; //END

        //== PUBLIC SHA1

        /**
         * Returns the SHA1 hash of a string
         *
         * @memberof smartJ$CryptoHash
         * @method sha1
         * @static
         *
         * @throws console.error
         *
         * @param {String} s The string
         * @param {Boolean} b64 If set to TRUE will use Base64 Encoding instead of Hex Encoding
         * @return {String} The SHA1 hash of the string
         */
        const sha1 = function(s, b64 = false) {
            //--
            if (b64 === true) {
                //--
                if (testCryptoHash_b64_SHA1 !== true) { // run test
                    if (sha1_b64(_Test$.unicodeString()) == _Test$.testSHA1(true)) {
                        testCryptoHash_b64_SHA1 = true; // test passed
                    } else { // test failed
                        _Test$.raiseError('SHA1/Hash/B64');
                        return '';
                    } //end if else
                } //end if
                //--
                return String(sha1_b64(s));
                //--
            } else {
                //--
                if (testCryptoHash_hex_SHA1 !== true) { // run test
                    if (sha1_hex(_Test$.unicodeString()) == _Test$.testSHA1()) {
                        testCryptoHash_hex_SHA1 = true; // test passed
                    } else { // test failed
                        _Test$.raiseError('SHA1/Hash/Hex');
                        return '';
                    } //end if else
                } //end if
                //--
                return String(sha1_hex(s));
                //--
            } //end if else
            //--
        }; //END
        _C$.sha1 = sha1; // export

        //== PRIVATES SHA512

        // Secure Hash Algorithm (SHA512), based on http://www.happycode.info

        // PRIVATE :: SHA512 :: encrypt (hex)
        const sha512_hex = (s) => {
            s = _Utils$.stringPureVal(s); // cast to string, don't trim ! need to preserve the value
            s = _Utils$.utf8_encode(s); // make it unicode
            //	return crypt_binb2hex(sha512_core(s));
            return _Utils$.bin2hex(crypt_binb2str(sha512_core(s)), true);
        }; //END

        // PRIVATE :: SHA512 :: encrypt (b64)
        const sha512_b64 = (s) => {
            s = _Utils$.stringPureVal(s); // cast to string, don't trim ! need to preserve the value
            s = _Utils$.utf8_encode(s); // make it unicode
            return _Ba$e64.encode(crypt_binb2str(sha512_core(s)), true);
        }; //END

        // PRIVATE :: SHA512
        const sha512_int_64 = function(msint_32, lsint_32) { // it is function constructor !
            const _M$ = this; // self referencing
            _M$.highOrder = msint_32;
            _M$.lowOrder = lsint_32;
        }; //END

        // PRIVATE :: SHA512
        const sha512_safeadd_2 = function(x, y) {
            //--
            let lsw, msw, lowOrder, highOrder;
            //--
            lsw = (x.lowOrder & 0xFFFF) + (y.lowOrder & 0xFFFF);
            msw = (x.lowOrder >>> 16) + (y.lowOrder >>> 16) + (lsw >>> 16);
            lowOrder = ((msw & 0xFFFF) << 16) | (lsw & 0xFFFF);
            //--
            lsw = (x.highOrder & 0xFFFF) + (y.highOrder & 0xFFFF) + (msw >>> 16);
            msw = (x.highOrder >>> 16) + (y.highOrder >>> 16) + (lsw >>> 16);
            highOrder = ((msw & 0xFFFF) << 16) | (lsw & 0xFFFF);
            //--
            return new sha512_int_64(highOrder, lowOrder);
            //--
        }; //END

        // PRIVATE :: SHA512
        const sha512_safeadd_4 = function(a, b, c, d) {
            //--
            let lsw, msw, lowOrder, highOrder;
            //--
            lsw = (a.lowOrder & 0xFFFF) + (b.lowOrder & 0xFFFF) + (c.lowOrder & 0xFFFF) + (d.lowOrder & 0xFFFF);
            msw = (a.lowOrder >>> 16) + (b.lowOrder >>> 16) + (c.lowOrder >>> 16) + (d.lowOrder >>> 16) + (lsw >>> 16);
            lowOrder = ((msw & 0xFFFF) << 16) | (lsw & 0xFFFF);
            //--
            lsw = (a.highOrder & 0xFFFF) + (b.highOrder & 0xFFFF) + (c.highOrder & 0xFFFF) + (d.highOrder & 0xFFFF) + (msw >>> 16);
            msw = (a.highOrder >>> 16) + (b.highOrder >>> 16) + (c.highOrder >>> 16) + (d.highOrder >>> 16) + (lsw >>> 16);
            highOrder = ((msw & 0xFFFF) << 16) | (lsw & 0xFFFF);
            //--
            return new sha512_int_64(highOrder, lowOrder);
            //--
        }; //END

        // PRIVATE :: SHA512
        const sha512_safeadd_5 = function(a, b, c, d, e) {
            //--
            let lsw, msw, lowOrder, highOrder;
            //--
            lsw = (a.lowOrder & 0xFFFF) + (b.lowOrder & 0xFFFF) + (c.lowOrder & 0xFFFF) + (d.lowOrder & 0xFFFF) + (e.lowOrder & 0xFFFF);
            msw = (a.lowOrder >>> 16) + (b.lowOrder >>> 16) + (c.lowOrder >>> 16) + (d.lowOrder >>> 16) + (e.lowOrder >>> 16) + (lsw >>> 16);
            lowOrder = ((msw & 0xFFFF) << 16) | (lsw & 0xFFFF);
            //--
            lsw = (a.highOrder & 0xFFFF) + (b.highOrder & 0xFFFF) + (c.highOrder & 0xFFFF) + (d.highOrder & 0xFFFF) + (e.highOrder & 0xFFFF) + (msw >>> 16);
            msw = (a.highOrder >>> 16) + (b.highOrder >>> 16) + (c.highOrder >>> 16) + (d.highOrder >>> 16) + (e.highOrder >>> 16) + (lsw >>> 16);
            highOrder = ((msw & 0xFFFF) << 16) | (lsw & 0xFFFF);
            //--
            return new sha512_int_64(highOrder, lowOrder);
            //--
        }; //END

        // PRIVATE :: SHA512
        const sha512_maj = (x, y, z) => {
            //--
            return new sha512_int_64(
                (x.highOrder & y.highOrder) ^ (x.highOrder & z.highOrder) ^ (y.highOrder & z.highOrder),
                (x.lowOrder & y.lowOrder) ^ (x.lowOrder & z.lowOrder) ^ (y.lowOrder & z.lowOrder)
            );
            //--
        }; //END

        // PRIVATE :: SHA512
        const sha512_ch = (x, y, z) => {
            //--
            return new sha512_int_64(
                (x.highOrder & y.highOrder) ^ (~x.highOrder & z.highOrder),
                (x.lowOrder & y.lowOrder) ^ (~x.lowOrder & z.lowOrder)
            );
            //--
        }; //END

        // PRIVATE :: SHA512
        const sha512_rot_r = (x, n) => {
            //--
            if (n <= 32) {
                return new sha512_int_64(
                    (x.highOrder >>> n) | (x.lowOrder << (32 - n)),
                    (x.lowOrder >>> n) | (x.highOrder << (32 - n))
                );
            } else {
                return new sha512_int_64(
                    (x.lowOrder >>> n) | (x.highOrder << (32 - n)),
                    (x.highOrder >>> n) | (x.lowOrder << (32 - n))
                );
            } //end if else
            //--
        }; //END

        // PRIVATE :: SHA512
        const sha512_sh_r = (x, n) => {
            //--
            if (n <= 32) {
                return new sha512_int_64(
                    x.highOrder >>> n,
                    x.lowOrder >>> n | (x.highOrder << (32 - n))
                );
            } else {
                return new sha512_int_64(
                    0,
                    x.highOrder << (32 - n)
                );
            } //end if else
            //--
        }; //END

        // PRIVATE :: SHA512
        const sha512_sigma_0 = function(x) {
            //--
            let rotr28 = sha512_rot_r(x, 28);
            let rotr34 = sha512_rot_r(x, 34);
            let rotr39 = sha512_rot_r(x, 39);
            //--
            return new sha512_int_64(
                rotr28.highOrder ^ rotr34.highOrder ^ rotr39.highOrder,
                rotr28.lowOrder ^ rotr34.lowOrder ^ rotr39.lowOrder
            );
            //--
        }; //END

        // PRIVATE :: SHA512
        const sha512_sigma_1 = function(x) {
            //--
            let rotr14 = sha512_rot_r(x, 14);
            let rotr18 = sha512_rot_r(x, 18);
            let rotr41 = sha512_rot_r(x, 41);
            //--
            return new sha512_int_64(
                rotr14.highOrder ^ rotr18.highOrder ^ rotr41.highOrder,
                rotr14.lowOrder ^ rotr18.lowOrder ^ rotr41.lowOrder
            );
            //--
        }; //END

        // PRIVATE :: SHA512
        const sha512_gamma_0 = function(x) {
            //--
            let rotr1 = sha512_rot_r(x, 1),
                rotr8 = sha512_rot_r(x, 8),
                shr7 = sha512_sh_r(x, 7);
            //--
            return new sha512_int_64(
                rotr1.highOrder ^ rotr8.highOrder ^ shr7.highOrder,
                rotr1.lowOrder ^ rotr8.lowOrder ^ shr7.lowOrder
            );
            //--
        }; //END

        // PRIVATE :: SHA512
        const sha512_gamma_1 = function(x) {
            //--
            let rotr19 = sha512_rot_r(x, 19);
            let rotr61 = sha512_rot_r(x, 61);
            let shr6 = sha512_sh_r(x, 6);
            //--
            return new sha512_int_64(
                rotr19.highOrder ^ rotr61.highOrder ^ shr6.highOrder,
                rotr19.lowOrder ^ rotr61.lowOrder ^ shr6.lowOrder
            );
            //--
        }; //END

        // PRIVATE :: SHA512
        const sha512_core = function(str) {
            //--
            let H = [
                new sha512_int_64(0x6a09e667, 0xf3bcc908), new sha512_int_64(0xbb67ae85, 0x84caa73b),
                new sha512_int_64(0x3c6ef372, 0xfe94f82b), new sha512_int_64(0xa54ff53a, 0x5f1d36f1),
                new sha512_int_64(0x510e527f, 0xade682d1), new sha512_int_64(0x9b05688c, 0x2b3e6c1f),
                new sha512_int_64(0x1f83d9ab, 0xfb41bd6b), new sha512_int_64(0x5be0cd19, 0x137e2179)
            ];
            //--
            let K = [
                new sha512_int_64(0x428a2f98, 0xd728ae22), new sha512_int_64(0x71374491, 0x23ef65cd),
                new sha512_int_64(0xb5c0fbcf, 0xec4d3b2f), new sha512_int_64(0xe9b5dba5, 0x8189dbbc),
                new sha512_int_64(0x3956c25b, 0xf348b538), new sha512_int_64(0x59f111f1, 0xb605d019),
                new sha512_int_64(0x923f82a4, 0xaf194f9b), new sha512_int_64(0xab1c5ed5, 0xda6d8118),
                new sha512_int_64(0xd807aa98, 0xa3030242), new sha512_int_64(0x12835b01, 0x45706fbe),
                new sha512_int_64(0x243185be, 0x4ee4b28c), new sha512_int_64(0x550c7dc3, 0xd5ffb4e2),
                new sha512_int_64(0x72be5d74, 0xf27b896f), new sha512_int_64(0x80deb1fe, 0x3b1696b1),
                new sha512_int_64(0x9bdc06a7, 0x25c71235), new sha512_int_64(0xc19bf174, 0xcf692694),
                new sha512_int_64(0xe49b69c1, 0x9ef14ad2), new sha512_int_64(0xefbe4786, 0x384f25e3),
                new sha512_int_64(0x0fc19dc6, 0x8b8cd5b5), new sha512_int_64(0x240ca1cc, 0x77ac9c65),
                new sha512_int_64(0x2de92c6f, 0x592b0275), new sha512_int_64(0x4a7484aa, 0x6ea6e483),
                new sha512_int_64(0x5cb0a9dc, 0xbd41fbd4), new sha512_int_64(0x76f988da, 0x831153b5),
                new sha512_int_64(0x983e5152, 0xee66dfab), new sha512_int_64(0xa831c66d, 0x2db43210),
                new sha512_int_64(0xb00327c8, 0x98fb213f), new sha512_int_64(0xbf597fc7, 0xbeef0ee4),
                new sha512_int_64(0xc6e00bf3, 0x3da88fc2), new sha512_int_64(0xd5a79147, 0x930aa725),
                new sha512_int_64(0x06ca6351, 0xe003826f), new sha512_int_64(0x14292967, 0x0a0e6e70),
                new sha512_int_64(0x27b70a85, 0x46d22ffc), new sha512_int_64(0x2e1b2138, 0x5c26c926),
                new sha512_int_64(0x4d2c6dfc, 0x5ac42aed), new sha512_int_64(0x53380d13, 0x9d95b3df),
                new sha512_int_64(0x650a7354, 0x8baf63de), new sha512_int_64(0x766a0abb, 0x3c77b2a8),
                new sha512_int_64(0x81c2c92e, 0x47edaee6), new sha512_int_64(0x92722c85, 0x1482353b),
                new sha512_int_64(0xa2bfe8a1, 0x4cf10364), new sha512_int_64(0xa81a664b, 0xbc423001),
                new sha512_int_64(0xc24b8b70, 0xd0f89791), new sha512_int_64(0xc76c51a3, 0x0654be30),
                new sha512_int_64(0xd192e819, 0xd6ef5218), new sha512_int_64(0xd6990624, 0x5565a910),
                new sha512_int_64(0xf40e3585, 0x5771202a), new sha512_int_64(0x106aa070, 0x32bbd1b8),
                new sha512_int_64(0x19a4c116, 0xb8d2d0c8), new sha512_int_64(0x1e376c08, 0x5141ab53),
                new sha512_int_64(0x2748774c, 0xdf8eeb99), new sha512_int_64(0x34b0bcb5, 0xe19b48a8),
                new sha512_int_64(0x391c0cb3, 0xc5c95a63), new sha512_int_64(0x4ed8aa4a, 0xe3418acb),
                new sha512_int_64(0x5b9cca4f, 0x7763e373), new sha512_int_64(0x682e6ff3, 0xd6b2b8a3),
                new sha512_int_64(0x748f82ee, 0x5defb2fc), new sha512_int_64(0x78a5636f, 0x43172f60),
                new sha512_int_64(0x84c87814, 0xa1f0ab72), new sha512_int_64(0x8cc70208, 0x1a6439ec),
                new sha512_int_64(0x90befffa, 0x23631e28), new sha512_int_64(0xa4506ceb, 0xde82bde9),
                new sha512_int_64(0xbef9a3f7, 0xb2c67915), new sha512_int_64(0xc67178f2, 0xe372532b),
                new sha512_int_64(0xca273ece, 0xea26619c), new sha512_int_64(0xd186b8c7, 0x21c0c207),
                new sha512_int_64(0xeada7dd6, 0xcde0eb1e), new sha512_int_64(0xf57d4f7f, 0xee6ed178),
                new sha512_int_64(0x06f067aa, 0x72176fba), new sha512_int_64(0x0a637dc5, 0xa2c898a6),
                new sha512_int_64(0x113f9804, 0xbef90dae), new sha512_int_64(0x1b710b35, 0x131c471b),
                new sha512_int_64(0x28db77f5, 0x23047d84), new sha512_int_64(0x32caab7b, 0x40c72493),
                new sha512_int_64(0x3c9ebe0a, 0x15c9bebc), new sha512_int_64(0x431d67c4, 0x9c100d4c),
                new sha512_int_64(0x4cc5d4be, 0xcb3e42b6), new sha512_int_64(0x597f299c, 0xfc657e2a),
                new sha512_int_64(0x5fcb6fab, 0x3ad6faec), new sha512_int_64(0x6c44198c, 0x4a475817)
            ];
            //--
            let W = new Array(64);
            let a, b, c, d, e, f, g, h, i, j;
            let T1, T2;
            //--
            const strlen = str.length * crypt_chrsz();
            str = crypt_str2binb(str);
            //--
            str[strlen >> 5] |= 0x80 << (24 - strlen % 32);
            str[(((strlen + 128) >> 10) << 5) + 31] = strlen;
            //--
            for (let i = 0; i < str.length; i += 32) {
                //--
                a = H[0];
                b = H[1];
                c = H[2];
                d = H[3];
                e = H[4];
                f = H[5];
                g = H[6];
                h = H[7];
                //--
                for (let j = 0; j < 80; j++) {
                    //--
                    if (j < 16) {
                        W[j] = new sha512_int_64(str[j * 2 + i], str[j * 2 + i + 1]);
                    } else {
                        W[j] = sha512_safeadd_4(sha512_gamma_1(W[j - 2]), W[j - 7], sha512_gamma_0(W[j - 15]), W[j - 16]);
                    } //end if else
                    //--
                    T1 = sha512_safeadd_5(h, sha512_sigma_1(e), sha512_ch(e, f, g), K[j], W[j]);
                    T2 = sha512_safeadd_2(sha512_sigma_0(a), sha512_maj(a, b, c));
                    //--
                    h = g;
                    g = f;
                    f = e;
                    e = sha512_safeadd_2(d, T1);
                    d = c;
                    c = b;
                    b = a;
                    a = sha512_safeadd_2(T1, T2);
                    //--
                } //end for
                //--
                H[0] = sha512_safeadd_2(a, H[0]);
                H[1] = sha512_safeadd_2(b, H[1]);
                H[2] = sha512_safeadd_2(c, H[2]);
                H[3] = sha512_safeadd_2(d, H[3]);
                H[4] = sha512_safeadd_2(e, H[4]);
                H[5] = sha512_safeadd_2(f, H[5]);
                H[6] = sha512_safeadd_2(g, H[6]);
                H[7] = sha512_safeadd_2(h, H[7]);
                //--
            } //end for
            //--
            let binarray = [];
            //--
            for (let i = 0; i < H.length; i++) {
                binarray.push(H[i].highOrder);
                binarray.push(H[i].lowOrder);
            } //end for
            //--
            return binarray;
            //--
        }; //END

        //== PUBLIC SHA512

        /**
         * Returns the SHA512 hash of a string
         *
         * @memberof smartJ$CryptoHash
         * @method sha512
         * @static
         *
         * @throws console.error
         *
         * @param {String} s The string
         * @param {Boolean} b64 If set to TRUE will use Base64 Encoding instead of Hex Encoding
         * @return {String} The SHA512 hash of the string
         */
        const sha512 = function(s, b64 = false) {
            //--
            if (b64 === true) {
                //--
                if (testCryptoHash_b64_SHA512 !== true) { // run test
                    if (sha512_b64(_Test$.unicodeString()) == _Test$.testSHA512(true)) {
                        testCryptoHash_b64_SHA512 = true; // test passed
                    } else { // test failed
                        _Test$.raiseError('SHA512/Hash/B64');
                        return '';
                    } //end if else
                } //end if
                //--
                return String(sha512_b64(s));
                //--
            } else {
                //--
                if (testCryptoHash_hex_SHA512 !== true) { // run test
                    if (sha512_hex(_Test$.unicodeString()) == _Test$.testSHA512()) {
                        testCryptoHash_hex_SHA512 = true; // test passed
                    } else { // test failed
                        _Test$.raiseError('SHA512/Hash/Hex');
                        return '';
                    } //end if else
                } //end if
                //--
                return String(sha512_hex(s));
                //--
            } //end if else
            //--
        }; //END
        _C$.sha512 = sha512; // export

        //== PRIVATES SHA256

        // PRIVATE :: SHA256 :: encrypt (hex)
        let sha256_cache_h = [];
        let sha256_cache_k = [];
        const sha256_hex = function(str) {
            //--
            // based on: https://github.com/geraintluff/sha256
            // license: Public Domain
            //--
            str = _Utils$.stringPureVal(str); // cast to string, don't trim ! need to preserve the value
            str = _Utils$.utf8_encode(str); // make it unicode
            //--
            const rightRotate = (value, amount) => (value >>> amount) | (value << (32 - amount));
            //--
            const mathPow = Math.pow;
            const maxWord = mathPow(2, 32);
            const lengthProperty = 'length';
            const lenStr = str[lengthProperty] * 8;
            //--
            let i, j; // Used as a counter across the whole file
            let result = '';
            //--
            let words = [];
            //--
            //* caching results is optional - remove/add slash from front of this line to toggle
            // Initial hash value: first 32 bits of the fractional parts of the square roots of the first 8 primes
            // (we actually calculate the first 64, but extra values are just ignored)
            let hash = sha256_cache_h = sha256_cache_h || [];
            // Round constants: first 32 bits of the fractional parts of the cube roots of the first 64 primes
            let k = sha256_cache_k = sha256_cache_k || [];
            let primeCounter = k[lengthProperty];
            /*/
            let hash = [], k = [];
            let primeCounter = 0;
            //*/
            if (primeCounter <= 0) { // only run once if cached (or run each time if not)
                let isComposite = {};
                for (let candidate = 2; primeCounter < 64; candidate++) {
                    if (!isComposite[candidate]) {
                        for (i = 0; i < 313; i += candidate) {
                            isComposite[i] = candidate;
                        } //end for
                        hash[primeCounter] = (mathPow(candidate, 0.5) * maxWord) | 0;
                        k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0;
                    } //end if
                } //end for
            } //end if
            //--
            str += '\x80'; // Append Ƈ' bit (plus zero padding)
            //--
            while (str[lengthProperty] % 64 - 56) {
                str += '\x00'; // More zero padding
            } //end while
            //--
            for (i = 0; i < str[lengthProperty]; i++) {
                j = str.charCodeAt(i);
                if (j >> 8) {
                    return ''; // ASCII check: only accept characters in range 0-255
                } //end if
                words[i >> 2] |= j << ((3 - i) % 4) * 8;
            } //end for
            //--
            words[words[lengthProperty]] = ((lenStr / maxWord) | 0);
            words[words[lengthProperty]] = (lenStr);
            //-- process each chunk
            for (j = 0; j < words[lengthProperty];) {
                //--
                let w = words.slice(j, j += 16); // The message is expanded into 64 words as part of the iteration
                let oldHash = hash;
                //-- This is now the undefinedworking hash", often labelled as variables a...g (we have to truncate as well, otherwise extra entries at the end accumulate
                hash = hash.slice(0, 8);
                for (i = 0; i < 64; i++) {
                    let i2 = i + j;
                    //-- Expand the message into 64 words ; Used below if
                    let w15 = w[i - 15],
                        w2 = w[i - 2];
                    //-- Iterate
                    let a = hash[0],
                        e = hash[4];
                    let temp1 = hash[7] + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) // S1
                        +
                        ((e & hash[5]) ^ ((~e) & hash[6])) // ch
                        +
                        k[i] +
                        (w[i] = (i < 16) ? w[i] : ( // Expand the message schedule if needed
                            w[i - 16] +
                            (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) // s0
                            +
                            w[i - 7] +
                            (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10)) // s1
                        ) | 0);
                    //-- This is only used once, so *could* be moved below, but it only saves 4 bytes and makes things unreadble
                    let temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) // S0
                        +
                        ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2])); // maj
                    //--
                    hash = [(temp1 + temp2) | 0].concat(hash); // We don't bother trimming off the extra ones, they're harmless as long as we're truncating when we do the slice()
                    hash[4] = (hash[4] + temp1) | 0;
                    //--
                } //end for
                //--
                for (i = 0; i < 8; i++) {
                    hash[i] = (hash[i] + oldHash[i]) | 0;
                } //end for
                //--
            } //end for
            //--
            for (i = 0; i < 8; i++) {
                for (j = 3; j + 1; j--) {
                    let b = (hash[i] >> (j * 8)) & 255;
                    result += ((b < 16) ? 0 : '') + b.toString(16);
                } //end for
            } //end for
            //--
            return String(result || '');
            //--
        }; // END

        // PRIVATE :: SHA256 :: encrypt (b64)
        const sha256_b64 = (s) => _Ba$e64.encode(_Utils$.hex2bin(sha256_hex(s), true), true); //END

        //== PUBLIC SHA256

        /**
         * Returns the SHA256 hash of a string
         *
         * @memberof smartJ$CryptoHash
         * @method sha256
         * @static
         *
         * @throws console.error
         *
         * @param {String} s The string
         * @param {Boolean} b64 If set to TRUE will use Base64 Encoding instead of Hex Encoding
         * @return {String} The SHA256 hash of the string
         */
        const sha256 = function(s, b64 = false) {
            //--
            if (b64 === true) {
                //--
                if (testCryptoHash_b64_SHA256 !== true) { // run test
                    if (sha256_b64(_Test$.unicodeString()) == _Test$.testSHA256(true)) {
                        testCryptoHash_b64_SHA256 = true; // test passed
                    } else { // test failed
                        _Test$.raiseError('SHA256/Hash/B64');
                        return '';
                    } //end if else
                } //end if
                //--
                return String(sha256_b64(s));
                //--
            } else {
                //--
                if (testCryptoHash_hex_SHA256 !== true) { // run test
                    if (sha256_hex(_Test$.unicodeString()) == _Test$.testSHA256()) {
                        testCryptoHash_hex_SHA256 = true; // test passed
                    } else { // test failed
                        _Test$.raiseError('SHA256/Hash/Hex');
                        return '';
                    } //end if else
                } //end if
                //--
                return String(sha256_hex(s));
                //--
            } //end if else
            //--
        }; //END
        _C$.sha256 = sha256; // export

    }
}; //END CLASS

smartJ$CryptoHash.secureClass(); // implements class security

if (typeof(window) != 'undefined') {
    window.smartJ$CryptoHash = smartJ$CryptoHash; // global export
} //end if

//=======================================
// CLASS :: Crypto Blowfish CBC
//=======================================

/*
 * Blowfish.js from Dojo Toolkit 1.8.1
 * License: New BSD License
 * Cut of by Sladex (xslade@gmail.com)
 * Based on the C# implementation by Marcus Hahn (http://www.hotpixel.net/)
 * Unsigned math based on Paul Johnstone and Peter Wood patches.
 * 2005-12-08
 */
// NOTICE: Max Key for Blowfish is up to 56 chars length (56 bytes = 448 bits)
// Modified by unixman: port to ES6, improved encoding handler by using double base64 compression to preserve also UTF-8 characters, implementing safe key derivation, ...
/**
 * CLASS :: Smart CryptoBlowfish (ES6, Strict Mode)
 *
 * @package Sf.Javascript:Crypto
 *
 * @requires		smartJ$Utils
 * @requires		smartJ$Base64
 * @requires		smartJ$BaseEncode
 * @requires		smartJ$CryptoHash
 *
 * @desc Blowfish (CBC) for JavaScript: Encrypt / Decrypt
 * @author unix-world.org
 * @license BSD
 * @file crypt_utils.js
 * @version 20220730
 * @class smartJ$CryptoBlowfish
 * @static
 * @frozen
 *
 */
smartJ$CryptoBlowfish = new class {
    constructor() { // STATIC CLASS (ES6)
        'use strict';
        const _N$ = 'smartJ$CryptoBlowfish';

        // :: static
        const _C$ = this; // self referencing

        const _p$ = console;

        let SECURED = false;
        _C$.secureClass = () => { // implements class security
            if (SECURED === true) {
                _p$.warn(_N$, 'Class is already SECURED');
            } else {
                SECURED = true;
                Object.freeze(_C$);
            } //end if
        }; //END

        const _Utils$ = smartJ$Utils;
        const _Ba$e64 = smartJ$Base64;
        const _Ba$eConv = smartJ$BaseEncode;
        const _Crypto$Hash = smartJ$CryptoHash;

        //== PRIVATE BLOWFISH

        // Cipher Modes: ECB:0, CBC:1, PCBC:2, CFB:3, OFB:4, CTR:5
        const cipherModes = {
            CBC: 1 // other modes are not supported by this implementation ; CBC mode is the only one compatible with the PHP Crypto Api ...
        };

        // Objects for processing Blowfish encryption/decryption
        const POW2 = Math.pow(2, 2);
        const POW3 = Math.pow(2, 3);
        const POW4 = Math.pow(2, 4);
        const POW8 = Math.pow(2, 8);
        const POW16 = Math.pow(2, 16);
        const POW24 = Math.pow(2, 24);

        // CBC mode initialization vector
        let iv = null;
        let IV = null;

        // Blowfish Data Object
        const boxes = {
            p: [
                0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
                0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
                0x9216d5d9, 0x8979fb1b
            ],
            s0: [
                0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
                0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
                0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
                0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
                0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
                0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
                0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
                0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
                0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
                0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
                0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
                0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
                0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
                0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
                0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
                0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
                0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
                0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
                0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
                0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
                0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
                0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
                0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
                0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
                0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
                0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
                0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
                0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
                0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
                0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
                0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
                0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
            ],
            s1: [
                0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
                0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
                0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
                0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
                0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
                0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
                0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
                0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
                0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
                0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
                0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
                0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
                0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
                0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
                0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
                0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
                0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
                0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
                0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
                0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
                0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
                0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
                0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
                0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
                0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
                0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
                0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
                0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
                0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
                0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
                0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
                0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
            ],
            s2: [
                0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
                0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
                0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
                0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
                0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
                0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
                0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
                0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
                0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
                0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
                0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
                0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
                0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
                0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
                0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
                0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
                0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
                0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
                0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
                0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
                0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
                0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
                0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
                0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
                0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
                0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
                0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
                0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
                0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
                0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
                0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
                0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
            ],
            s3: [
                0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
                0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
                0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
                0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
                0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
                0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
                0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
                0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
                0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
                0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
                0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
                0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
                0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
                0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
                0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
                0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
                0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
                0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
                0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
                0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
                0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
                0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
                0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
                0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
                0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
                0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
                0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
                0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
                0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
                0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
                0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
                0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
            ]
        };

        const bfsig = 'b' + 'f' + (56 * 8) + '.' + 'v' + (16 / 8);

        //== PRIVATES BLOWFISH

        const array_map = function(arr, callback, theObj, Ctr) {
            //--
            // summary:
            //		applies callback to each element of arr and returns
            //		an Array with the results
            // arr: Array|String
            //		the array to iterate on. If a string, operates on
            //		individual characters.
            // callback: Function|String
            //		a function is invoked with three arguments, (item, index,
            //		array),	 and returns a value
            // theObj: Object?
            //		may be used to scope the call to callback
            // returns: Array
            // description:
            //		The function corresponds to the JavaScript 1.6 Array.map() method, with one difference: when
            //		run over sparse arrays, the implementation passes the 'holes' in the sparse array to
            //		the callback function with a value of undefined. JavaScript 1.6's map skips the holes in the sparse array.
            //		For more details, see:
            //		https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map
            // example:
            //	| // returns [2, 3, 4, 5]
            //	| array.map([1, 2, 3, 4], function(item){ return item+1 });
            // TODO: why do we have a non-standard signature here? do we need 'Ctr' ?
            //--
            let i = 0,
                l = arr && arr.length || 0,
                out = new(Ctr || Array)(l);
            if (l && (typeof(arr) == 'string')) {
                arr = arr.split('');
            } //end if
            if (typeof(callback) == 'string') {
                callback = cache[callback] || buildFn(callback);
            } //end if
            //--
            if (theObj) {
                for (; i < l; ++i) {
                    out[i] = callback.call(theObj, arr[i], i, arr);
                } //end for
            } else {
                for (; i < l; ++i) {
                    out[i] = callback(arr[i], i, arr);
                } //end for
            } //end if else
            //--
            return out; // Array
            //--
        }; //END

        // fixes based on patch submitted by Peter Wood (#5791)

        const add = (x, y) => {
            return (((x >> 0x10) + (y >> 0x10) + (((x & 0xffff) + (y & 0xffff)) >> 0x10)) << 0x10) | (((x & 0xffff) + (y & 0xffff)) & 0xffff);
        }; //END

        const xor = (x, y) => {
            return (((x >> 0x10) ^ (y >> 0x10)) << 0x10) | (((x & 0xffff) ^ (y & 0xffff)) & 0xffff);
        }; //END

        const dollar = function(v, box) {
            //--
            let d = box.s3[v & 0xff];
            v >>= 8;
            let c = box.s2[v & 0xff];
            v >>= 8;
            let b = box.s1[v & 0xff];
            v >>= 8;
            let a = box.s0[v & 0xff];
            //--
            let r;
            r = (((a >> 0x10) + (b >> 0x10) + (((a & 0xffff) + (b & 0xffff)) >> 0x10)) << 0x10) | (((a & 0xffff) + (b & 0xffff)) & 0xffff);
            r = (((r >> 0x10) ^ (c >> 0x10)) << 0x10) | (((r & 0xffff) ^ (c & 0xffff)) & 0xffff);
            //--
            return (((r >> 0x10) + (d >> 0x10) + (((r & 0xffff) + (d & 0xffff)) >> 0x10)) << 0x10) | (((r & 0xffff) + (d & 0xffff)) & 0xffff);
            //--
        }; //END

        const eb = function(o, box) {
            //--
            let l = o.left;
            let r = o.right;
            //--
            l = xor(l, box.p[0]);
            r = xor(r, xor(dollar(l, box), box.p[1]));
            l = xor(l, xor(dollar(r, box), box.p[2]));
            r = xor(r, xor(dollar(l, box), box.p[3]));
            l = xor(l, xor(dollar(r, box), box.p[4]));
            r = xor(r, xor(dollar(l, box), box.p[5]));
            l = xor(l, xor(dollar(r, box), box.p[6]));
            r = xor(r, xor(dollar(l, box), box.p[7]));
            l = xor(l, xor(dollar(r, box), box.p[8]));
            r = xor(r, xor(dollar(l, box), box.p[9]));
            l = xor(l, xor(dollar(r, box), box.p[10]));
            r = xor(r, xor(dollar(l, box), box.p[11]));
            l = xor(l, xor(dollar(r, box), box.p[12]));
            r = xor(r, xor(dollar(l, box), box.p[13]));
            l = xor(l, xor(dollar(r, box), box.p[14]));
            r = xor(r, xor(dollar(l, box), box.p[15]));
            l = xor(l, xor(dollar(r, box), box.p[16]));
            //--
            o.right = l;
            o.left = xor(r, box.p[17]);
            //--
        }; //END

        const db = function(o, box) {
            //--
            let l = o.left;
            let r = o.right;
            //--
            l = xor(l, box.p[17]);
            r = xor(r, xor(dollar(l, box), box.p[16]));
            l = xor(l, xor(dollar(r, box), box.p[15]));
            r = xor(r, xor(dollar(l, box), box.p[14]));
            l = xor(l, xor(dollar(r, box), box.p[13]));
            r = xor(r, xor(dollar(l, box), box.p[12]));
            l = xor(l, xor(dollar(r, box), box.p[11]));
            r = xor(r, xor(dollar(l, box), box.p[10]));
            l = xor(l, xor(dollar(r, box), box.p[9]));
            r = xor(r, xor(dollar(l, box), box.p[8]));
            l = xor(l, xor(dollar(r, box), box.p[7]));
            r = xor(r, xor(dollar(l, box), box.p[6]));
            l = xor(l, xor(dollar(r, box), box.p[5]));
            r = xor(r, xor(dollar(l, box), box.p[4]));
            l = xor(l, xor(dollar(r, box), box.p[3]));
            r = xor(r, xor(dollar(l, box), box.p[2]));
            l = xor(l, xor(dollar(r, box), box.p[1]));
            //--
            o.right = l;
            o.left = xor(r, box.p[0]);
            //--
        }; //END

        // Return true if it is a String
        const isString = (it) => {
            return (typeof(it) == 'string' || it instanceof String); // Boolean
        }; //END

        //	Note that we aren't caching contexts here; it might take a little longer but we should be more secure like so.
        const init = function(key) {
            //--
            let k = key;
            //--
            if (isString(k)) {
                k = array_map(k.split(''), (item) => {
                    return item.charCodeAt(0) & 0xff;
                });
            } //end if
            //-- init the boxes
            let pos = 0,
                data = 0;
            let res = {
                left: 0,
                right: 0
            };
            let i, j, l;
            let box = {
                p: array_map(boxes.p.slice(0), (item) => {
                    let q = k.length,
                        r;
                    for (r = 0; r < 4; r++) {
                        data = (data * POW8) | k[pos++ % q];
                    } //end for
                    return (((item >> 0x10) ^ (data >> 0x10)) << 0x10) | (((item & 0xffff) ^ (data & 0xffff)) & 0xffff);
                }),
                s0: boxes.s0.slice(0),
                s1: boxes.s1.slice(0),
                s2: boxes.s2.slice(0),
                s3: boxes.s3.slice(0)
            };
            //-- encrypt p and the s boxes
            for (i = 0, l = box.p.length; i < l;) {
                eb(res, box);
                box.p[i++] = res.left, box.p[i++] = res.right;
            } //end for
            for (i = 0; i < 4; i++) {
                for (j = 0, l = box['s' + i].length; j < l;) {
                    eb(res, box);
                    box['s' + i][j++] = res.left, box['s' + i][j++] = res.right;
                } //end for
            } //end for
            //--
            return box;
            //--
        }; //END

        const setKey = function(key) { // {{{SYNC-CRYPTO-KEY-DERIVE}}}
            //--
            const _m$ = 'setKey';
            //-- generate a secure key {{{SYNC-BLOWFISH-KEY}}}
            key = _Utils$.stringPureVal(key, true); // trim // {{{SYNC-CRYPTO-KEY-TRIM}}}
            //--
            let klen = key.length;
            if (klen < 7) { // {{{SYNC-CRYPTO-KEY-MIN}}}
                _p$.warn(_N$, _m$, 'Key Size is lower than 7 bytes (' + klen + ') !');
                return '';
            } else if (klen > 4096) { // {{{SYNC-CRYPTO-KEY-MAX}}}
                _p$.warn(_N$, _m$, 'Key Size is higher than 4096 bytes (' + klen + ') !');
                return '';
            } //end if
            //--
            const nByte = '\0';
            const salted_key = String(nByte + key);
            //-- chances are zero in practice to have a key colission by ensuring 2 different (salted and not salted) input to produce a simultan colission in all 5 algos: CRC32 / MD5 / SHA1 / SHA256 / SHA512 at once !!!
            const hkey1 = String(_Crypto$Hash.crc32b(key) + nByte + _Crypto$Hash.md5(key) + nByte + _Crypto$Hash.sha1(key) + nByte + _Crypto$Hash.sha256(key) + nByte + _Crypto$Hash.sha512(key));
            const hkey2 = String(_Crypto$Hash.crc32b(salted_key) + nByte + _Crypto$Hash.md5(salted_key) + nByte + _Crypto$Hash.sha1(salted_key) + nByte + _Crypto$Hash.sha256(salted_key) + nByte + _Crypto$Hash.sha512(salted_key));
            const composed_key = String(hkey1 + nByte + hkey2);
            const derived_key = String(_Ba$eConv.base_from_hex_convert(_Crypto$Hash.sha256(composed_key), 92)) + "'" + String(_Ba$eConv.base_from_hex_convert(_Crypto$Hash.md5(composed_key), 92));
            const r_key = String(derived_key).substr(0, 448 / 8); // 448/8
            //_p$.log(_N$, _m$, 'Key:', r_key);
            //--
            return String(r_key);
            //--
        }; //END

        // sets the initialization vector to data
        const setIV = function(key) { // {{{SYNC-CRYPTO-KEY-DERIVE}}}
            //--
            const _m$ = 'setIV';
            //--
            key = _Utils$.stringPureVal(key, true); // trim
            let klen = key.length;
            if (klen < 7) { // {{{SYNC-CRYPTO-KEY-MIN}}}
                _p$.warn(_N$, _m$, 'Key Size is lower than 7 bytes (' + klen + ') !');
                return '';
            } else if (klen > 4096) { // {{{SYNC-CRYPTO-KEY-MAX}}}
                _p$.warn(_N$, _m$, 'Key Size is higher than 4096 bytes (' + klen + ') !');
                return '';
            } //end if
            //-- SmartFramework compatible {{{SYNC-BLOWFISH-IV}}}
            const data = String(_Crypto$Hash.crc32b(key, true)).padStart(8, '0');
            IV = String(String(data) + ':' + _Crypto$Hash.sha1(key, true)).substr(0, 64 / 8); // 64/8
            //_p$.log(_N$, _m$, 'iV:', IV);
            //--
            const byt = array_map(IV.split(''), (item) => {
                return item.charCodeAt(0);
            }); // pre-process
            iv = {}; // make it a pair of words
            iv.left = byt[0] * POW24 | byt[1] * POW16 | byt[2] * POW8 | byt[3];
            iv.right = byt[4] * POW24 | byt[5] * POW16 | byt[6] * POW8 | byt[7];
            //--
            return String(IV);
            //--
        }; //END

        //== PUBLIC BLOWFISH

        /**
         * Blowfish encrypts (CBC) plaintext using an encryption key
         *
         * @memberof smartJ$CryptoBlowfish
         * @method encrypt
         * @static
         *
         * @param {String} plaintext The plain string
         * @param {String} key The encryption key
         * @return {String} The Blowfish encrypted string
         */
        const encrypt = function(plaintext, key) {
            //--
            const _m$ = 'encrypt';
            //--
            plaintext = _Utils$.stringPureVal(plaintext); // cast to string, don't trim ! need to preserve the value
            if (plaintext == '') {
                return '';
            } //end if
            plaintext = _Ba$e64.encode(plaintext); // b64 is because is not UTF-8 safe and may corrupt unicode characters
            //--
            key = _Utils$.stringPureVal(key); // cast to string, don't trim ! need to preserve the value
            const testIV = setIV(key); // needs original key
            key = setKey(key);
            //--
            if (key.length != 56) {
                _p$.error(_N$, _m$, 'Invalid Key Length (req. 448 bytes)');
                return '';
            } //end if
            if (testIV.length != 8) {
                _p$.error(_N$, _m$, 'Invalid iV Length (req. 64 bytes)');
                return '';
            } //end if
            //--
            const mode = cipherModes.CBC;
            const bx = init(key);
            //-- {{{SYNC-BLOWFISH-CHECKSUM}}}
            plaintext = String(plaintext + '#CKSUM256#' + _Crypto$Hash.sha256(plaintext, true));
            //--
            //===== {{{SYNC-BLOWFISH-PADDING}}}
            //-- Blowfish is a 64-bit block cipher. It means that the data must be provided in units that are a multiple of 8 bytes
            const padding = 8 - (plaintext.length & 7);
            //-- unixman: fix: add spaces as padding as we have it as b64 encoded and will not modify the original
            // for(let i=0; i<padding; i++) { plaintext+=String.fromCharCode(padding); } // original padding
            for (let i = 0; i < padding; i++) {
                plaintext += ' '; // unixman (pad with spaces)
            } //end for
            //--
            //=====
            //--
            let cipher = [];
            const count = plaintext.length >> 3;
            let pos = 0,
                o = {};
            const isCBC = (mode == cipherModes.CBC);
            const vector = {
                left: iv.left || null,
                right: iv.right || null
            };
            //--
            for (let i = 0; i < count; i++) {
                //--
                o.left = plaintext.charCodeAt(pos) * POW24 |
                    plaintext.charCodeAt(pos + 1) * POW16 |
                    plaintext.charCodeAt(pos + 2) * POW8 |
                    plaintext.charCodeAt(pos + 3);
                o.right = plaintext.charCodeAt(pos + 4) * POW24 |
                    plaintext.charCodeAt(pos + 5) * POW16 |
                    plaintext.charCodeAt(pos + 6) * POW8 |
                    plaintext.charCodeAt(pos + 7);
                //--
                if (isCBC) {
                    o.left = (((o.left >> 0x10) ^ (vector.left >> 0x10)) << 0x10) | (((o.left & 0xffff) ^ (vector.left & 0xffff)) & 0xffff);
                    o.right = (((o.right >> 0x10) ^ (vector.right >> 0x10)) << 0x10) | (((o.right & 0xffff) ^ (vector.right & 0xffff)) & 0xffff);
                } //end if
                //-- encrypt the block
                eb(o, bx);
                //--
                if (isCBC) {
                    vector.left = o.left;
                    vector.right = o.right;
                } //end if
                //--
                cipher.push((o.left >> 24) & 0xff);
                cipher.push((o.left >> 16) & 0xff);
                cipher.push((o.left >> 8) & 0xff);
                cipher.push(o.left & 0xff);
                cipher.push((o.right >> 24) & 0xff);
                cipher.push((o.right >> 16) & 0xff);
                cipher.push((o.right >> 8) & 0xff);
                cipher.push(o.right & 0xff);
                //--
                pos += 8;
                //--
            } //end for
            //--
            //=====
            //-- BASE64
            //	return _Ba$e64.encode(array_map(cipher, (item) => (String.fromCharCode(item))).join(''), true); // b64
            return bfsig + '!' + _Ba$eConv.b64s_enc(array_map(cipher, (item) => (String.fromCharCode(item))).join(''), true); // b64s
            //-- HEX
            //	return String(array_map(cipher, (item) => ((item<=0xf?'0':'') + item.toString(16))).join('').toUpperCase()); // HEX
            //--
            //=====
            //--
        }; //END
        _C$.encrypt = encrypt; // export

        /**
         * Blowfish decrypts (CBC) ciphertext using the encryption key
         *
         * @memberof smartJ$CryptoBlowfish
         * @method decrypt
         * @static
         *
         * @param {String} ciphertext The Blowfish encrypted string
         * @param {String} key The encryption key
         * @return {String} The decrypted string
         */
        const decrypt = function(ciphertext, key) {
            //--
            const _m$ = 'decrypt';
            //--
            ciphertext = _Utils$.stringPureVal(ciphertext, true); // cast to string, + trim
            if (ciphertext == '') {
                return '';
            } //end if
            if (!_Utils$.stringStartsWith(ciphertext, bfsig + '!')) {
                return '';
            } //end if
            ciphertext = ciphertext.split('!', 2);
            ciphertext = _Utils$.stringPureVal(ciphertext[1], true); // cast to string, + trim
            //--
            key = _Utils$.stringPureVal(key); // cast to string, don't trim ! need to preserve the value
            const testIV = setIV(key); // needs original key
            key = setKey(key);
            //--
            if (key.length != 56) {
                _p$.error(_N$, _m$, 'Invalid Key Length (req. 448 bytes)');
                return '';
            } //end if
            if (testIV.length != 8) {
                _p$.error(_N$, _m$, 'Invalid iV Length (req. 64 bytes)', testIV);
                return '';
            } //end if
            //--
            const mode = cipherModes.CBC;
            const bx = init(key);
            //--
            let pt = [];
            let c = null;
            //--
            //=====
            //-- BASE64
            //	c = array_map(_Ba$e64.decode(ciphertext, true).split(''), function(item){ return item.charCodeAt(0); }); // b64
            c = array_map(_Ba$eConv.b64s_dec(ciphertext, true).split(''), function(item) {
                return item.charCodeAt(0);
            }); // b64s
            //-- HEX
            //	c = [];
            //	ciphertext = _Utils$.stringTrim(ciphertext).toLowerCase(); // make back lowercase and trim (because in encrypt we delivered as uppercase)
            //	for(let i=0, l=ciphertext.length-1; i<l; i+=2){
            //		c.push(parseInt(ciphertext.substr(i,2), 16));
            //	} //end for
            //--
            ciphertext = null; // free mem
            //=====
            //--
            const count = c.length >> 3;
            let pos = 0,
                o = {};
            const isCBC = (mode == cipherModes.CBC);
            const vector = {
                left: iv.left || null,
                right: iv.right || null
            };
            //--
            for (let i = 0; i < count; i++) {
                //--
                o.left = c[pos] * POW24 | c[pos + 1] * POW16 | c[pos + 2] * POW8 | c[pos + 3];
                o.right = c[pos + 4] * POW24 | c[pos + 5] * POW16 | c[pos + 6] * POW8 | c[pos + 7];
                //--
                let left = null,
                    right = null;
                if (isCBC) {
                    left = o.left;
                    right = o.right;
                } //end if
                //-- decrypt the block
                db(o, bx);
                //--
                if (isCBC) {
                    o.left = (((o.left >> 0x10) ^ (vector.left >> 0x10)) << 0x10) | (((o.left & 0xffff) ^ (vector.left & 0xffff)) & 0xffff);
                    o.right = (((o.right >> 0x10) ^ (vector.right >> 0x10)) << 0x10) | (((o.right & 0xffff) ^ (vector.right & 0xffff)) & 0xffff);
                    vector.left = left;
                    vector.right = right;
                } //end if
                //--
                pt.push((o.left >> 24) & 0xff);
                pt.push((o.left >> 16) & 0xff);
                pt.push((o.left >> 8) & 0xff);
                pt.push(o.left & 0xff);
                pt.push((o.right >> 24) & 0xff);
                pt.push((o.right >> 16) & 0xff);
                pt.push((o.right >> 8) & 0xff);
                pt.push(o.right & 0xff);
                pos += 8;
                //--
            } //end for
            //===== {{{SYNC-BLOWFISH-PADDING-TRIM}}} :: trim padding spaces
            // #un-padding# is not necessary as we added trailing spaces and we simply trim it below
            //if(pt[pt.length-1] == pt[pt.length-2]||pt[pt.length-1]==0x01) { let n = pt[pt.length-1]; pt.splice(pt.length-n, n); }
            //--
            let plaintext = String(_Utils$.stringTrim(array_map(pt, (item) => String.fromCharCode(item)).join('')));
            //=====
            //-- {{{SYNC-BLOWFISH-CHECKSUM}}}
            let parts = plaintext.split('#CKSUM256#', 2);
            plaintext = _Utils$.stringPureVal(parts[0], true); // trim
            const checksum = _Utils$.stringPureVal(parts[1], true); // trim
            parts = null;
            if (_Crypto$Hash.sha256(plaintext, true) !== String(checksum)) {
                _p$.warn(_N$, 'decrypt', 'Checksum Failed'); // do not raise error just alert
                return '';
            } //end if
            //-- convert to string and reverse b64 :: b64 is because is not UTF-8 safe and may corrupt unicode characters
            return String(_Ba$e64.decode(plaintext)); // string
            //--
        }; //END
        _C$.decrypt = decrypt; // export

    }
}; //END CLASS

smartJ$CryptoBlowfish.secureClass(); // implements class security

if (typeof(window) != 'undefined') {
    window.smartJ$CryptoBlowfish = smartJ$CryptoBlowfish; // global export
} //end if

//=======================================
// CLASS :: Crypto DH Kx
//=======================================

/**
 * CLASS :: Smart DH Kx (ES6)
 *
 * @package Sf.Javascript:DhKx
 *
 * @requires		smartJ$Utils
 *
 * @desc The JavaScript class provides methods to implement a secure algorithm for Diffie-Hellman key exchange between a server and a client ; Supports dual operation mode (Int64 or BigInt ; for using BigInt the broser must support it ...)
 * @author unix-world.org
 * @license BSD
 * @file crypt_utils.js
 * @version 20220730
 * @class smartJ$DhKx
 * @static
 * @frozen
 *
 */
const smartJ$DhKx = new class {
    constructor() { // STATIC CLASS (ES6)
        'use strict';
        const _N$ = 'smartJ$DhKx';

        // :: static
        const _C$ = this; // self referencing

        const _p$ = console;

        let SECURED = false;
        _C$.secureClass = () => { // implements class security
            if (SECURED === true) {
                _p$.warn(_N$, 'Class is already SECURED');
            } else {
                SECURED = true;
                Object.freeze(_C$);
            } //end if
        }; //END

        let _w$ = (typeof(window) != 'undefined') ? window : null;

        const _Utils$ = smartJ$Utils;
        const _Ba$eConv = smartJ$BaseEncode;
        const _Crypto$Hash = smartJ$CryptoHash;
        const _Crypto$Blowfish = smartJ$CryptoBlowfish;

        //==

        const _Option$ = ((typeof(smartJ$Options) != 'undefined') && smartJ$Options && (smartJ$Options.DhKx != undefined) && (typeof(smartJ$Options) === 'object') && (typeof(smartJ$Options.DhKx) === 'object')) ? smartJ$Options.DhKx : null;

        const bigIntSupport = ((_Option$ && (!!_Option$.BigIntSupport)) || (_w$ && _w$.crypto && _w$.crypto.getRandomValues && _w$.BigInt)) ? true : false;

        _C$.param_Size = (_Option$ && (typeof(_Option$.Size) == 'string') && _Option$.Size) ? _Utils$.stringTrim(_Option$.Size) : 'default';
        _C$.param_Prix = (_Option$ && (typeof(_Option$.Prix) == 'string') && _Option$.Prix) ? _Utils$.stringTrim(_Option$.Prix) : 'default';
        _C$.param_UseBigInt = (bigIntSupport && (_Option$ && (!!_Option$.UseBigInt))) ? true : false;
        _C$.param_DevMode = (_Option$ && (!!_Option$.DevMode)) ? true : false;

        _w$ = null;

        //== [PUBLIC]

        /**
         * Get the DH Mode
         *
         * @memberof smartJ$DhKx
         * @method getMode
         * @static
         *
         * @return 	{String} 				The operating mode: BigInt or Int64
         */
        const getMode = function() {
            //--
            let mode = '??';
            if (_C$.param_UseBigInt === true) {
                mode = 'BigInt';
            } else {
                mode = 'Int64';
            } //end if else
            //--
            return String(mode);
            //--
        }; //END
        _C$.getMode = getMode; // export

        /**
         * Get the DH Base (Gen)
         *
         * @memberof smartJ$DhKx
         * @method getBaseGen
         * @static
         *
         * @return 	{Mixed} 				The random base generator ; size depends if running in BigInt or Int64 mode
         */
        const getBaseGen = function(size) {
            //--
            size = _Utils$.stringPureVal(size, true); // trim
            if ((size === '') || (size === 'default')) {
                size = _C$.param_Size;
            } //end if
            //--
            return rng(String(size));
            //--
        }; //END
        _C$.getBaseGen = getBaseGen; // export

        /**
         * Get the DH SRV Side Data
         *
         * @memberof smartJ$DhKx
         * @method getSrvData
         * @static
         *
         * @param 	{Mixed} basegen			The random base generator ; size depends if running in BigInt or Int64 mode
         * @return 	{Object} 				The SRV side Data
         */
        const getSrvData = function(basegen) {
            //--
            const size = String(_C$.param_Size);
            const prix = String(_C$.param_Prix);
            const p = prime(prix);
            const ssec = rng(size);
            const spub = powm(basegen, ssec, p);
            //_p$.log(_N$, 'getSrvData', 'base:', basegen, 'p:', p, 'ssec:', ssec, 'spub:', spub);
            //--
            return {
                base: basegen,
                prix: prix,
                sec: ssec,
                pub: spub,
            };
            //--
        }; //END
        _C$.getSrvData = getSrvData; // export

        /**
         * Get the DH SRV Side Shad
         *
         * @memberof smartJ$DhKx
         * @method getSrvShad
         * @static
         *
         * @param 	{Mixed} ssec			The random side of SRV
         * @param 	{Mixed} ssec			The public side of CLI
         * @return 	{Mixed} 				The SRV side Shad Data ; size depends if running in BigInt or Int64 mode
         */
        const getSrvShad = function(ssec, cpub) {
            //--
            const prix = String(_C$.param_Prix);
            const p = prime(prix);
            const shad = powm(cpub, ssec, p);
            //_p$.log(_N$, 'getSrvShad', 'p:', p, 'ssec:', ssec, 'cpub:', cpub, 'shad:', shad);
            //--
            return shadizer(shad);
            //--
        }; //END
        _C$.getSrvShad = getSrvShad; // export

        /**
         * Get the DH CLI Side Data
         *
         * @memberof smartJ$DhKx
         * @method getCliData
         * @static
         *
         * @param 	{Mixed} basegen			The random base generator ; size depends if running in BigInt or Int64 mode
         * @return 	{Object} 				The CLI side Data
         */
        const getCliData = function(basegen) {
            //--
            const size = String(_C$.param_Size);
            const prix = String(_C$.param_Prix);
            const p = prime(prix);
            const csec = rng(size);
            const cpub = powm(basegen, csec, p);
            //_p$.log(_N$, 'getCliData', 'base:', basegen, 'p:', p, 'csec:', csec, 'cpub:', cpub);
            //--
            return {
                base: basegen,
                prix: prix,
                sec: csec,
                pub: cpub,
            };
            //--
        }; //END
        _C$.getCliData = getCliData; // export

        /**
         * Get the DH CLI Side Shad
         *
         * @memberof smartJ$DhKx
         * @method getCliShad
         * @static
         *
         * @param 	{Mixed} ssec			The random side of CLI
         * @param 	{Mixed} ssec			The public side of SRV
         * @return 	{Mixed} 				The CLI side Shad Data ; size depends if running in BigInt or Int64 mode
         */
        const getCliShad = function(csec, spub) {
            //--
            const prix = String(_C$.param_Prix);
            const p = prime(prix);
            const shad = powm(spub, csec, p);
            //_p$.log(_N$, 'getCliShad', 'p:', p, 'csec:', csec, 'spub:', spub, 'shad:', shad);
            //--
            return shadizer(shad);
            //--
        }; //END
        _C$.getCliShad = getCliShad; // export

        /*
         * Get the DH partial data (cli) derived from any valid idz data
         *
         * @private internal use
         *
         * @memberof smartJ$DhKx
         * @method getIdzShadData
         * @static
         *
         * @param 	{String} idz			The idz
         * @return 	{Object} 				The data object (shad)
         */
        const getIdzShadData = function(idz) {
            //--
            idz = _Utils$.stringPureVal(idz, true);
            //--
            let err = '';
            let cliShad = '';
            if (idz == '') {
                err = 'Empty Idz';
            } else {
                let arr = idxtizer(idz);
                if (arr.err && (arr.err != '')) {
                    err = String(arr.err);
                } else {
                    cliShad = _Utils$.stringPureVal(getCliShad(arr.csec, arr.spub), true);
                    if (cliShad == '') {
                        err = 'Empty Shad';
                    } //end if
                } //end if
            } //end if
            //--
            return {
                type: 'IdzShadData',
                mode: String(getMode()),
                size: String(_C$.param_Size),
                prix: String(_C$.param_Prix),
                shad: String(cliShad),
                err: String(err),
            };
            //--
        }; //END
        _C$.getIdzShadData = getIdzShadData; // export

        //== [DEVELOPMENT ONLY]

        /*
         * This method returns the full DhKx exchange data
         * This should be used just for development purposes only ... !!!
         *
         * @private development mode only
         *
         * @memberof smartJ$DhKx
         * @method getData
         * @static
         *
         * @return 	{Object} 				The data object
         */
        const getData = function() {
            //--
            const mode = String(getMode());
            //--
            if (_C$.param_DevMode !== true) {
                const errMsg = 'Development Mode is N/A';
                _p$.warn(_N$, 'getData', errMsg);
                return {
                    err: String(errMsg)
                };
            } //end if
            _p$.log(_N$, '[NOTICE]: getData: development mode is enabled', '[' + mode + ']');
            //--
            const basegen = getBaseGen(_C$.param_Size);
            //--
            const srvData = getSrvData(basegen);
            const cliData = getCliData(basegen);
            const srvShad = getSrvShad(srvData.sec, cliData.pub);
            const cliShad = getCliShad(cliData.sec, srvData.pub);
            //--
            let err = '';
            if ((srvShad == undefined) || (srvShad == '') || (srvShad == '0') || (srvShad !== cliShad)) {
                err = 'Shad Mismatch';
            } //end if
            //--
            return {
                type: 'Data',
                mode: String(mode),
                size: String(_C$.param_Size),
                prix: String(_C$.param_Prix),
                prim: String(prime(String(_C$.param_Prix))),
                basegen: String(basegen),
                srv: {
                    sec: String(srvData.sec),
                    pub: String(srvData.pub),
                    shad: String(srvShad),
                },
                cli: {
                    sec: String(cliData.sec),
                    pub: String(cliData.pub),
                    shad: String(cliShad),
                },
                idz: String(idatizer(cliData.sec, srvData.pub)),
                err: String(err),
            };
            //--
        }; //END
        _C$.getData = getData; // export

        //== [PRIVATES]

        // iddatizer
        const idxtizer = function(idz) {
            //--
            idz = _Utils$.stringPureVal(idz, true);
            if (idz == '') {
                return {
                    'err': 'Invalid IDZ (1)'
                };
            } //end if
            //--
            if (!_Utils$.stringContains(idz, '!')) {
                return {
                    'err': 'Invalid IDZ (2)'
                };
            } //end if
            //--
            const arr = idz.split('!');
            if (arr.length !== 3) {
                return {
                    'err': 'Invalid IDZ (3)'
                };
            } //end if
            const pfx = 'dH.';
            const ver = 'v1';
            let sig = '';
            let mod = '0';
            if (_C$.param_UseBigInt === true) {
                sig = 'iHg.';
                mod = '1';
            } else {
                sig = 'i64.';
                mod = '2';
            } //end if else
            if (arr[0] !== pfx + sig + ver) {
                return {
                    'err': 'Invalid IDZ (4.' + mod + ')'
                };
            } //end if
            //--
            arr[1] = _Utils$.stringPureVal(_Ba$eConv.b64s_dec(arr[1]), true);
            if (arr[1] == '') {
                return {
                    'err': 'Invalid IDZ (5)'
                };
            } //end if
            //--
            arr[2] = _Utils$.stringPureVal(_Ba$eConv.b64s_dec(arr[2]), true);
            if (arr[2] == '') {
                return {
                    'err': 'Invalid IDZ (6)'
                };
            } //end if
            //--
            const bk = _Utils$.stringTrim(_Ba$eConv.base_to_hex_convert(arr[2], 85));
            if (bk == '') {
                return {
                    'err': 'Invalid IDZ (7)'
                };
            } //end if
            arr[2] = _Utils$.stringTrim(String(_Utils$.stringTrim(_Utils$.hex2bin(bk))).substr(1));
            if (arr[2] == '') {
                return {
                    'err': 'Invalid IDZ (8)'
                };
            } //end if
            //--
            arr[1] = _Utils$.stringPureVal(_Crypto$Blowfish.decrypt(String(arr[1]), _Ba$eConv.base_from_hex_convert(_Crypto$Hash.sha256('&=' + bk + '#'), 92)), true);
            if (arr[1] == '') {
                return {
                    'err': 'Invalid IDZ (9)'
                };
            } //end if
            arr[1] = _Utils$.stringTrim(String(_Utils$.hex2bin(_Utils$.stringTrim(_Ba$eConv.base_to_hex_convert(arr[1], 92)))).substr(1));
            if (arr[1] == '') {
                return {
                    'err': 'Invalid IDZ (10)'
                };
            } //end if
            //--
            return {
                'csec': String(arr[1]),
                'spub': String(arr[2]),
            };
            //--
        };

        // iddatizer
        const idatizer = function(csec, spub) {
            //--
            let shd = 'dH';
            //--
            if (_C$.param_UseBigInt === true) {
                shd += '.iHg';
            } else {
                shd += '.i64';
            } //end if else
            //--
            const bk = _Utils$.bin2hex('@' + spub);
            //--
            return String(shd + '.v1!' + _Ba$eConv.b64s_enc(_Crypto$Blowfish.encrypt(_Ba$eConv.base_from_hex_convert(_Utils$.bin2hex('$' + csec), 92), _Ba$eConv.base_from_hex_convert(_Crypto$Hash.sha256('&=' + bk + '#'), 92))) + '!' + _Ba$eConv.b64s_enc(_Ba$eConv.base_from_hex_convert(bk, 85)));
            //--
        }; //END

        // hexfixer
        const evenhexlen = function(shx) {
            //--
            shx = _Utils$.stringPureVal(shx, true);
            //--
            const len = shx.length;
            if (len <= 0) {
                shx = '00'; // this should not happen but anyway, it have to be fixed just in the case
            } else if ((len % 2) !== 0) {
                shx = '0' + shx; // even zeros padding
            } //end if
            //--
            return String(shx);
            //--
        }; //END

        // shaddowizer
        const shadizer = function(shad) {
            //--
            let shr = '';
            //--
            if (_C$.param_UseBigInt === true) {
                const shx = String(evenhexlen(shad.toString(16)));
                shr = _Ba$eConv.base_from_hex_convert(shx, 92);
            } else {
                const shx = String(evenhexlen(shad.toString(16)));
                shr = _Ba$eConv.base_from_hex_convert(shx, 85) + "'" + _Ba$eConv.base_from_hex_convert(shx, 62) + "'" + _Ba$eConv.base_from_hex_convert(shx, 92) + "'" + _Ba$eConv.base_from_hex_convert(shx, 58);
            } //end if else
            //--
            return String(shr);
            //--
        }; //END

        // randomizer
        const rng = (size) => {
            //--
            //_p$.log(_N$, 'rng', 'param_UseBigInt', _C$.param_UseBigInt);
            if (_C$.param_UseBigInt === true) {
                return rngBigint(size);
            } else {
                return rngInt64(size);
            } //end if else
            //--
        }; //END

        // pwr deriv by prim
        const powm = (a, b, pri) => {
            //--
            //_p$.log(_N$, 'powm', 'param_UseBigInt', _C$.param_UseBigInt);
            if (_C$.param_UseBigInt === true) {
                return powmBigint(a, b, pri);
            } else {
                return powmInt64(a, b, pri);
            } //end if else
            //--
        }; //END

        // primes ...
        const prime = (prix) => {
            //--
            //_p$.log(_N$, 'prime', 'param_UseBigInt', _C$.param_UseBigInt);
            if (_C$.param_UseBigInt === true) {
                return primeBigint(prix);
            } else {
                return primeInt64(prix);
            } //end if else
            //--
        }; //END

        //== [SPECIFIC PRIVATES: Int64 and BigInt]

        // Int64 randomizer
        const rngInt64 = function(size) {
            //--
            size = _Utils$.stringPureVal(size, true);
            if ((size === '') || (size === 'default')) {
                size = 24;
            } //end if
            size = Math.ceil(size);
            switch (size) {
                case 12:
                case 16:
                case 24:
                    break;
                default:
                    size = 24;
                    _p$.warn(_N$, 'rngInt64: Invalid Size Selection, using defaults:', size);
            } //end switch
            //_p$.log(_N$, 'rngInt64', 'size', size);
            //--
            let rnd = ~~(Math.random() * (Math.pow(2, size) - 1)) >>> 0; // math rand can be 1 thus for safety using 2^52 (-4 as using 1000 as base) = 2^48 instead of 2^53 ; Javascript Number.MAX_SAFE_INTEGER is 2^53 - 1 ; the reasoning behind that number is that JavaScript uses double-precision floating-point format numbers as specified in IEEE 754 and can only safely represent integers between -(2^53 - 1) and 2^53 - 1 so need adjustement in this context
            if (rnd <= 0) {
                rnd = 1;
            } //end if
            //--
            return rnd;
            //--
        }; //END

        // BigInt randomizer
        const rngBigint = function(size) {
            //--
            size = _Utils$.stringPureVal(size, true);
            if ((size === '') || (size === 'default')) {
                size = 16;
            } //end if
            size = Math.ceil(size);
            switch (size) {
                case 128:
                case 96:
                case 64:
                case 48:
                case 32:
                case 16:
                case 8:
                    break;
                default:
                    size = 16;
                    _p$.warn(_N$, 'rngBigint: Invalid Size Selection, using defaults:', size);
            } //end switch
            //_p$.log(_N$, 'rngBigint', 'size', size);
            //--
            const randoms = new Uint32Array(size); // allocate space for four 32-bit numbers
            //	window.crypto.getRandomValues(randoms); // get random values (browser dependent)
            for (let i = 0, l = randoms.length; i < l; i++) {
                randoms[i] = Math.floor(Math.random() * 256); // get random values (browser independent)
            } //end for
            //--
            return String(Array.from(randoms).map(elem => String(elem)).join('')); // join numbers together as string
            //--
        }; //END

        // Int64 pwr deriv by prim ; https://stackoverflow.com/questions/24677932/diffie-hellman-key-exchange-with-javascript-sometimes-wrong
        const powmInt64 = (a, b, pri) => {
            //--
            if (b <= 0) {
                return 1;
            } else if (b === 1) {
                return a % pri;
            } else if (b % 2 === 0) {
                return powmInt64((a * a) % pri, b / 2 | 0, pri) % pri;
            } else {
                return (powmInt64((a * a) % pri, b / 2 | 0, pri) * a) % pri;
            } //end if else
            //--
        }; //END

        // BigInt pwr deriv by prim
        const powmBigint = (a, b, pri) => { // must return only BigInt values ; BigInt will automatically trim off decimals as: 5n / 2n = 2n (not 2.5n)
            //--
            a = BigInt(a);
            b = BigInt(b);
            pri = BigInt(pri);
            if (b <= BigInt(0)) {
                return BigInt(1);
            } else if (b == BigInt(1)) {
                return a % pri;
            } else if (b % BigInt(2) == BigInt(0)) {
                return powmBigint((a * a) % pri, b / BigInt(2), pri) % pri;
            } else {
                return powmBigint((a * a) % pri, b / BigInt(2), pri) * a % pri;
            } //end if else
            //--
        }; //END

        // Int64 primes ...
        const primeInt64 = function(prix) {
            //--
            const primesInt64 = [ // max js safe int is: 9007199254740992 ; of which sqrt is: ~ 94906265 (length: 8)
                72419213, 54795931, 32926051, 21801887, 77635013, 25470191, 77639819, 42010253,
                33563273, 32792339, 15923857, 67022173, 84250253, 67680727, 63438329, 52164643,
                51603269, 61444631, 58831133, 55711141, 73596863, 48905489, 61642963, 53812273,
                16600799, 79158229, 56490361, 73391389, 64351751, 14227727, 40517299, 95234563,
                42913363, 63566527, 52338703, 80146337, 37597201, 93581269, 32547497, 75587359,
                26024821, 57042743, 13862969, 46496719, 42787387, 29830469, 59912407, 75206447,
                40343341, 72357113, 23434063, 24336373, 39422399, 12866611, 11592293, 83937899,
                79746883, 37997129, 76431193, 67774627, 72107393, 31363271, 30388361, 25149569,
                54104161, 50575709, 70327973, 54960077, 92119793, 80615231, 38967139, 65609657,
                66432673, 56145097, 73864853, 70708361, 23913011, 35283481, 58352201, 57881491,
                89206109, 70619069, 96913759, 66156679, 63395257, 70022237, 93547543, 10891057,
                75492367, 86902223, 33054397, 36325571, 49119293, 64100537, 31986431, 16636237,
            ]; // 0x00 .. 0x5F
            //--
            prix = _Utils$.stringPureVal(prix, true);
            if ((prix === '') || (prix === 'default')) {
                prix = -1;
            } //end if
            prix = Math.floor(prix);
            let px = primesInt64[47]; // 0x2F
            if ((prix >= 0) && (prix < primesInt64.length)) {
                px = primesInt64[prix];
            } else if (prix !== -1) {
                _p$.warn(_N$, 'prime: Invalid Prime Selection (Int64), using defaults:', prix);
            } //end if
            //--
            return '0x' + px.toString(16);
            //--
        }; //END

        // BigInt primes ...
        const primeBigint = function(prix) {
            //-- {{{SYNC-DHKX-HIGH-PRIMES}}}
            const hcBase1 = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63';
            const hcBase2 = '7ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE';
            const hcBase3 = '45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA';
            const hcBase4 = '18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AA';
            const hcBase5 = 'AC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A9';
            const hcBase6 = '2108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C9340';
            const hcBase7 = '2849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6D';
            const primesBigint = {
                h017: '0x1141317432f7b89',
                h031: '0x6febe061005175e46c896e4079',
                h047: '0xf3f2b0ee30050c5f6bfcb9df1b9454e77bc3503',
                h061: '0x4771cfc3c2b8ad4561cb5437132e35e8398e8f956a2f2c94c51',
                h097: '0x426f09b2b25aba6bbcbf9ca5edb660b91d033440916732af9ae175a84afb665a25b392361c6952119',
                h127: '0x2c6121e6b14ecf756c083544de0e0933cac90dbeb6239905bfbec764527bbb4166ff832a2bcc3b4d6f634eddd30e40634adbbb5bfd',
                h257: '0x279e569032f0c7256218b58ad6418aa0e9436be424ab8f1431b1f9e6b5814e0ebda0ff65ef085d7e73fee51744dec07fe08c1a1cc65855630ca983927ca277406ac42094064387d65aeaa849f9bf449e04df8cb0e99a44b004ce0efca3386f1e82c078723cd265288d9a41',
                h232c1: '0x' + hcBase1 + 'A3620FFFFFFFFFFFFFFFF',
                h309c2: '0x' + hcBase1 + hcBase2 + '65381FFFFFFFFFFFFFFFF',
                h463c5: '0x' + hcBase1 + hcBase2 + hcBase3 + '237327FFFFFFFFFFFFFFFF', // 1536-bit MODP
                h617c14: '0x' + hcBase1 + hcBase2 + hcBase3 + hcBase4 + 'CAA68FFFFFFFFFFFFFFFF', // 2048-bit MODP (default)
                h925c15: '0x' + hcBase1 + hcBase2 + hcBase3 + hcBase4 + hcBase5 + '3AD2CAFFFFFFFFFFFFFFFF', // 3072-bit MODP
                h1234c16: '0x' + hcBase1 + hcBase2 + hcBase3 + hcBase4 + hcBase5 + hcBase6 + '63199FFFFFFFFFFFFFFFF', // 4096-bit MODP
                h1850c17: '0x' + hcBase1 + hcBase2 + hcBase3 + hcBase4 + hcBase5 + hcBase6 + hcBase7 + 'CC4024FFFFFFFFFFFFFFFF', // 6144-bit MODP
                h2467c18: '0x' + hcBase1 + hcBase2 + hcBase3 + hcBase4 + hcBase5 + hcBase6 + hcBase7 + 'BE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF', // 8192-bit MODP
            };
            //console.log("DhKx High Primes", primesBigint);
            //--
            let px = null;
            prix = _Utils$.stringPureVal(prix, true);
            if (prix === '') {
                prix = 'default';
            } //end if
            switch (String(prix)) {
                case 'h017':
                case 'h031':
                case 'h047':
                case 'h061':
                case 'h097':
                case 'h127':
                case 'h257':
                case 'h232c1':
                case 'h309c2':
                case 'h463c5':
                case 'h617c14':
                case 'h925c15':
                case 'h1234c16':
                case 'h1850c17':
                case 'h2467c18':
                    px = primesBigint[String(prix)];
                    break;
                default:
                    if (prix !== 'default') {
                        _p$.warn(_N$, 'prime: Invalid Prime Selection (Bigint), using defaults:', prix);
                    } //end if
                    px = primesBigint['h127'];
            } //end switch
            //--
            return String(px);
            //--
        }; //END

    }
}; //END CLASS

smartJ$DhKx.secureClass(); // implements class security

if (typeof(window) != 'undefined') {
    window.smartJ$DhKx = smartJ$DhKx; // global export
} //end if

//=======================================
//=======================================

//==================================================================
//==================================================================

// #END

// ===== ifmodalbox.js

// [LIB - Smart.Framework / JS / Smart Modal iFrame]
// (c) 2006-2022 unix-world.org - all rights reserved
// r.8.7 / smart.framework.v.8.7

// DEPENDS: jQuery, smartJ$Utils
// DEPENDS-OPTIONAL: smartJ$Browser (for scanner only)

//==================================================================
//==================================================================

//================== [ES6]

/**
 * CLASS :: Smart ModalBox (ES6)
 *
 * @package Sf.Javascript:Browser
 *
 * @requires		jQuery
 * @requires		smartJ$Utils
 * @requires		*smartJ$Browser (optional, for scanner only)
 *
 * @desc a Modal iFrame component for JavaScript / jQuery
 * @author unix-world.org
 * @license BSD
 * @file ifmodalbox.js
 * @version 20220730
 * @class smartJ$ModalBox
 * @fires iFrame: Show / Load / Unload / Hide
 * @listens getHandlerOnBeforeUnload() that can be set by setHandlerOnBeforeUnload(()=>{})
 * @static
 * @frozen
 *
 */
const smartJ$ModalBox = new class {
    constructor() { // STATIC CLASS
        const _N$ = 'smartJ$ModalBox';
        const VER = 'r.20220730';

        // :: static
        const _C$ = this; // self referencing

        const _p$ = console;

        let SECURED = false;
        _C$.secureClass = () => { // implements class security
            if (SECURED === true) {
                _p$.warn(_N$, 'Class is already SECURED');
            } else {
                SECURED = true;
                Object.freeze(_C$);
            } //end if
        }; //END

        const $ = jQuery; // jQuery referencing

        const _Option$ = ((typeof(smartJ$Options) != 'undefined') && smartJ$Options && (typeof(smartJ$Options) === 'object') && (smartJ$Options.ModalBox != undefined) && (typeof(smartJ$Options.ModalBox) === 'object')) ? smartJ$Options.ModalBox : null;

        const _Utils$ = smartJ$Utils;
        //	const _BwUtils$ = smartJ$Browser; // needed for scanner only ; because the smart box loads before it it throws referencing the class here in ES6 !


        //== privates

        //-- private settings, access only as readonly or use get/set methods
        let iFBoxStatus = ''; // hold the status: '' | 'visible'
        let iFBoxRefreshState = 0; // if=1, will refresh parent
        let iFBoxRefreshURL = ''; // ^=1 -> if != '' will redirect parent
        //--

        //-- private registry
        let iFBoxWidth = 200; // current width, min is 200
        let iFBoxHeight = 100; // current height, min is 100
        let iFBoxBeforeUnload = null; // null or method to execute before unload that can be set external
        //--

        //-- private const
        const iFBoxPrefix = 'smart__iFModalBox_';
        const iFBoxName = iFBoxPrefix + '_iFrame';
        const iFBoxBackground = iFBoxPrefix + '_Bg';
        const iFBoxDiv = iFBoxPrefix + '_Div';
        const iFBoxBtnClose = iFBoxPrefix + '_X';
        const iFBoxLoader = iFBoxPrefix + '_Ldr';
        const iFBoxBtnTTlClose = '[X]';
        //--

        //== setup: can be changed after loading the script

        /**
         * Use Protection used to allow (when TRUE) the click on overlay to close the modal
         * @default false
         * @let {Boolean} param_UseProtection
         * @set [before] smartJ$Options.ModalBox.UseProtection ; [after] can be changed by setting the 2nd param of the LoadURL() method to TRUE or FALSE, will persist
         * @get N/A
         * @static
         * @memberof smartJ$ModalBox
         */
        let param_UseProtection = (_Option$ && (!!_Option$.UseProtection)) ? 1 : 0; // 1 protect ; 0 not protect (eval as boolean) ; it is variable not constant !

        /**
         * Loader Image used to display when loading ...
         * @default 'lib/js/framework/img/loading.svg'
         * @const {String} param_LoaderImg
         * @set [before] smartJ$Options.ModalBox.LoaderImg
         * @get N/A
         * @static
         * @memberof smartJ$ModalBox
         */
        const param_LoaderImg = (_Option$ && (typeof(_Option$.LoaderImg) == 'string') && _Option$.LoaderImg) ? _Utils$.stringTrim(_Option$.LoaderImg) : 'lib/js/framework/img/loading.svg';

        /**
         * Loader Blank HTML Page used to clear and free memory before loading or after unloading ...
         * @default 'lib/js/framework/loading.html'
         * @const {String} param_LoaderBlank
         * @set [before] smartJ$Options.ModalBox.LoaderBlank
         * @get N/A
         * @static
         * @private
         * @memberof smartJ$ModalBox
         */
        const param_LoaderBlank = (_Option$ && (typeof(_Option$.LoaderBlank) == 'string') && _Option$.LoaderBlank) ? _Utils$.stringTrim(_Option$.LoaderBlank) : 'lib/js/framework/loading.html';

        /**
         * Close Button Image
         * @default 'lib/js/framework/img/close.svg'
         * @const {String} param_CloseImg
         * @set [before] smartJ$Options.ModalBox.CloseImg
         * @get N/A
         * @static
         * @memberof smartJ$ModalBox
         */
        const param_CloseImg = (_Option$ && (typeof(_Option$.CloseImg) == 'string') && _Option$.CloseImg) ? _Utils$.stringTrim(_Option$.CloseImg) : 'lib/js/framework/img/close.svg';

        /**
         * Close Button Horizontal Align Mode :: 'left' or 'right'
         * @default 'right'
         * @const {String} param_CloseAlign
         * @set [before] smartJ$Options.ModalBox.CloseAlign
         * @get N/A
         * @static
         * @memberof smartJ$ModalBox
         */
        const param_CloseAlign = (_Option$ && (_Option$.CloseAlign === 'left')) ? 'left' : 'right';

        /**
         * Close Button Customization: alternate html code for the modal close button ... can be used to completely replace the above param_CloseImg
         * @default ''
         * @const {String} param_CloseBtnAltHtml
         * @set [before] smartJ$Options.ModalBox.CloseBtnAltHtml
         * @get N/A
         * @static
         * @private
         * @memberof smartJ$ModalBox
         */
        const param_CloseBtnAltHtml = (_Option$ && (typeof(_Option$.CloseBtnAltHtml) == 'string') && _Option$.CloseBtnAltHtml) ? _Utils$.stringTrim(_Option$.CloseBtnAltHtml) : '';

        /**
         * Close Box Vertical Align Mode :: 'top' or 'middle' / 'center'
         * @default 'top'
         * @const {String} param_vAlign
         * @set [before] smartJ$Options.ModalBox.vAlign
         * @get N/A
         * @static
         * @memberof smartJ$ModalBox
         */
        const param_vAlign = (_Option$ && (typeof(_Option$.vAlign) == 'string') && ((_Option$.vAlign == 'middle') || (_Option$.vAlign == 'center'))) ? String(_Option$.vAlign) : 'top';

        /**
         * Modal iFrame Open Delay (500 ... 1000)
         * @default 850
         * @const {Integer+} param_DelayOpen
         * @set [before] smartJ$Options.ModalBox.DelayOpen
         * @get N/A
         * @static
         * @private
         * @memberof smartJ$ModalBox
         */
        const param_DelayOpen = (_Option$ && (typeof(_Option$.DelayOpen) == 'number') && _Option$.DelayOpen && _Utils$.isFiniteNumber(_Option$.DelayOpen)) ? _Utils$.format_number_int(_Option$.DelayOpen, false) : 850;

        /**
         * Modal iFrame Close Delay (250 ... 750)
         * @default 500
         * @const {Integer+} param_DelayClose
         * @set [before] smartJ$Options.ModalBox.DelayClose
         * @get N/A
         * @static
         * @private
         * @memberof smartJ$ModalBox
         */
        const param_DelayClose = (_Option$ && (typeof(_Option$.DelayClose) == 'number') && _Option$.DelayClose && _Utils$.isFiniteNumber(_Option$.DelayClose)) ? _Utils$.format_number_int(_Option$.DelayClose, false) : 500;

        /**
         * Overlay Customization - Background Color
         * Allowed Values: hexa color between '#000000' .. '#FFFFFF'
         * @default '#333333'
         * @const {String} param_CssOverlayBgColor
         * @set [before] smartJ$Options.ModalBox.CssOverlayBgColor
         * @get N/A
         * @static
         * @memberof smartJ$ModalBox
         */
        const param_CssOverlayBgColor = (_Option$ && (typeof(_Option$.CssOverlayBgColor) == 'string') && _Option$.CssOverlayBgColor && String(_Option$.CssOverlayBgColor).match(/^\#([0-9a-f]{6})$/i)) ? _Utils$.stringTrim(_Option$.CssOverlayBgColor) : '#333333';

        /**
         * Overlay Customization - Opacity
         * Allowed Values: between 0 and 1
         * @default 0.85
         * @const {Float} param_CssOverlayOpacity
         * @set [before] smartJ$Options.ModalBox.CssOverlayOpacity
         * @get N/A
         * @static
         * @memberof smartJ$ModalBox
         */
        const param_CssOverlayOpacity = (_Option$ && (typeof(_Option$.CssOverlayOpacity) == 'number') && _Utils$.isFiniteNumber(_Option$.CssOverlayOpacity) && (_Utils$.format_number_float(_Option$.CssOverlayOpacity, false) >= 0) && (_Utils$.format_number_float(_Option$.CssOverlayOpacity, false) <= 1)) ? _Utils$.format_number_dec(_Option$.CssOverlayOpacity, 2, false, true) : 0.85;

        //==


        /**
         * Get the Name of Smart Modal Box
         *
         * @public
         *
         * @memberof smartJ$ModalBox
         * @method getName
         * @static
         * @arrow
         *
         * @return {String} The name of the Modal Box
         */
        const getName = () => {
            //--
            return String(iFBoxName);
            //--
        }; //END
        _C$.getName = getName; // export


        /**
         * Get the Status of Smart Modal Box
         *
         * @public
         *
         * @memberof smartJ$ModalBox
         * @method getStatus
         * @static
         * @arrow
         *
         * @return {String} The status of the Modal Box as: 'visible' or ''
         */
        const getStatus = () => {
            //--
            return String(iFBoxStatus);
            //--
        }; //END
        _C$.getStatus = getStatus; // export


        /**
         * Get the Version of Smart Modal Box
         *
         * @private
         *
         * @memberof smartJ$ModalBox
         * @method getVersion
         * @static
         * @arrow
         *
         * @return {String} The version of the Modal Box
         */
        const getVersion = () => {
            //--
            return String(VER);
            //--
        }; //END
        _C$.getVersion = getVersion; // export, hidden


        /**
         * Set/Unset the Refresh Parent State/URL for Smart Modal Box
         *
         * @memberof smartJ$ModalBox
         * @method setRefreshParent
         * @static
         * @arrow
         *
         * @param {Boolean} state :: TRUE will SET / FALSE will UNSET
         * @param {String} yURL the Refresh URL that will execute on destruct of the Modal Box
         */
        const setRefreshParent = (state, yURL) => {
            //--
            yURL = _Utils$.stringPureVal(yURL, true); // cast to string, trim
            //--
            if (!!state) {
                iFBoxRefreshState = 1;
                iFBoxRefreshURL = String(yURL);
            } else {
                iFBoxRefreshState = 0;
                iFBoxRefreshURL = '';
            } //end if else
            //--
        }; //END
        _C$.setRefreshParent = setRefreshParent; // export


        /**
         * Set Per-Instance Before Unload custom Handler: ()=>{} // return true or false; }
         *
         * @memberof smartJ$ModalBox
         * @method setHandlerOnBeforeUnload
         * @static
         * @arrow
         *
         * @param {Function} fx :: if type FUNCTION, will set the iFBoxBeforeUnload handler else will log an error
         * @return {Boolean} If fx is function and set, will return TRUE else FALSE
         */
        const setHandlerOnBeforeUnload = (fx) => {
            //--
            if (typeof(fx) === 'function') {
                iFBoxBeforeUnload = fx;
                return true;
            } //end if
            //--
            _p$.error(_N$, 'ERR: setHandlerOnBeforeUnload', 'fx is not a function');
            //--
            return false;
        }; // END
        _C$.setHandlerOnBeforeUnload = setHandlerOnBeforeUnload; // export


        /**
         * Make the Smart Modal Box to Load a new URL ; after load will show
         *
         * @memberof smartJ$ModalBox
         * @method LoadURL
         * @static
         *
         * @param {String} yURL :: the URL to be loaded ; must differ from the URL loaded in parent !
         * @param {Boolean} yProtect :: default is NULL ; if TRUE will protect closing Modal Box by Escape or click outside and can be closed only by close button
         * @param {Integer} windowWidth :: the width of the Modal Box
         * @param {Integer} windowHeight :: the height of the Modal Box
         */
        const LoadURL = function(yURL, yProtect = null, windowWidth = 0, windowHeight = 0) {
            //-- checks
            yURL = _Utils$.stringPureVal(yURL, true); // cast to string, trim
            //-- register
            iFBoxStatus = 'visible';
            if (yProtect !== null) {
                param_UseProtection = yProtect ? 1 : 0;
            } //end if
            iFBoxWidth = _Utils$.format_number_int(parseInt(windowWidth), false); // do not adjust value here, can have px as suffix
            iFBoxHeight = _Utils$.format_number_int(parseInt(windowHeight), false); // do not adjust value here, can have px as suffix
            //-- disable parent scrolling
            $('body').css({
                'overflow': 'hidden' // need to be hidden
            });
            //-- show loading
            $('#' + iFBoxLoader).empty();
            if (param_LoaderImg) {
                $('#' + iFBoxLoader).html('<br><br><img src="' + _Utils$.escape_html(param_LoaderImg) + '" alt="..." title="...">');
            } //end if
            //-- positioning
            executePositioning(param_UseProtection, iFBoxWidth, iFBoxHeight);
            //-- force no-cache and fix a bug if same URL as parent
            const UrlTime = new Date().getTime();
            if (yURL.indexOf('?') != -1) {
                yURL += '&';
            } else {
                yURL += '?';
            } //end if else
            yURL += String(iFBoxName + '=' + _Utils$.escape_url(UrlTime));
            //--
            $('#' + iFBoxName).show().css({
                'width': '100%',
                'height': '100%',
                'visibility': 'hidden' // BugFix: we use opacity to hide/show iFrame because some bug in browsers if the iframe is hidden while loading
            }).attr('src', String(yURL));
            //--
            let the_closebtn;
            if (param_CloseBtnAltHtml === '') {
                the_closebtn = '<img id="ifrm-close" src="' + _Utils$.escape_html(param_CloseImg) + '" alt="' + _Utils$.escape_html(iFBoxBtnTTlClose) + '" title="' + _Utils$.escape_html(iFBoxBtnTTlClose) + '">';
            } else {
                the_closebtn = String(param_CloseBtnAltHtml);
            } //end if else
            //--
            let the_align_left = 'auto';
            let the_align_right = 'auto';
            if (param_CloseAlign === 'left') {
                the_align_left = '-20px'; // left
            } else { // right
                the_align_right = '-20px'; // right
            } //end if else
            //--
            $('#' + iFBoxBtnClose).show().css({
                'position': 'absolute',
                'z-index': 2111111099, //9999999,
                'cursor': 'pointer',
                'top': '-12px',
                'left': the_align_left,
                'right': the_align_right,
                'min-width': '32px',
                'max-width': '64px',
                'min-height': '32px',
                'max-height': '64px',
                'visibility': 'hidden'
            }).empty().html(the_closebtn).click(() => {
                UnloadURL();
            });
            //--
            if (!yProtect) {
                $('#' + iFBoxBackground).click(() => {
                    UnloadURL();
                });
            } else {
                $('#' + iFBoxBackground).unbind('click');
            } //end if
            //-- show delayed
            let openDelay = _Utils$.format_number_int(param_DelayOpen, false);
            if (openDelay < 500) {
                openDelay = 500;
            } //end if
            if (openDelay > 1000) {
                openDelay = 1000;
            } //end if
            setTimeout(() => {
                makeVisible();
            }, openDelay); // delay a bit to avoid show a blank area
            //--
            return false;
            //--
        }; //END
        _C$.LoadURL = LoadURL; // export, hidden


        /**
         * Make the Smart Modal Box to Unload the URL ; after unload will hide
         *
         * @memberof smartJ$ModalBox
         * @method UnloadURL
         * @static
         */
        const UnloadURL = function() {
            //--
            let test_unload = true;
            try {
                test_unload = !!getHandlerOnBeforeUnload(); // boolean
            } catch (err) {
                _p$.error(_N$, 'ERR: UnloadURL', err);
                test_unload = true;
            } //end try catch
            if (!test_unload) {
                return false; // it is like onbeforeunload
            } //end if
            //--
            executeUnload();
            //--
            let closeDelay = _Utils$.format_number_int(param_DelayClose, false);
            if (closeDelay < 250) {
                closeDelay = 250;
            } //end if
            if (closeDelay > 750) {
                closeDelay = 750;
            } //end if
            //--
            setTimeout(() => {
                initialize();
            }, closeDelay); // delayed close
            //--
            return false;
            //--
        }; //END
        _C$.UnloadURL = UnloadURL; // export, hidden


        //================================== # [PRIVATES]


        const initialize = function() {
            //--
            //window.onresize = () => { // clear window resize
            //};
            //--
            $('#' + iFBoxDiv).css({
                'position': 'absolute',
                'width': '1px',
                'height': '1px',
                'left': '0px',
                'top': '0px'
            }).hide();
            //--
            $('#' + iFBoxBackground).css({
                'position': 'absolute',
                'width': '1px',
                'height': '1px',
                'left': '0px',
                'top': '0px'
            }).hide();
            //--
            if (iFBoxRefreshState) { // {{{SYNC-MODAL-Refresh-Parent-By-EXEC}}}
                //--
                const url = _Utils$.stringTrim(iFBoxRefreshURL);
                //--
                if (url == '') {
                    self.location = self.location; // FIX from above line: avoid reload to resend POST vars !!
                } else {
                    self.location = String(url);
                } //end if else
                //--
                iFBoxRefreshState = 0;
                iFBoxRefreshURL = '';
                //--
            } //end if
            //--
            return false;
            //--
        }; //END
        // no export


        const makeVisible = () => {
            //--
            $('#' + iFBoxBtnClose).css({
                'visibility': 'visible'
            });
            //--
            $('#' + iFBoxName).css({
                'background-color': '#FFFFFF',
                'visibility': 'visible' // BugFix: we use opacity to hide/show iFrame because some bug in browsers if the iframe is hidden while loading
            });
            $('#' + iFBoxLoader).empty().html('');
            //--
            return false;
            //--
        }; //END
        // no export


        const getHandlerOnBeforeUnload = () => {
            //--
            if (typeof(iFBoxBeforeUnload) === 'function') {
                return !!iFBoxBeforeUnload(); // boolean
            } //end if
            //--
            return true;
            //--
        }; //END
        // no export


        const getWindowWidth = (windowWidth) => {
            //--
            windowWidth = _Utils$.format_number_int(parseInt(windowWidth), false); // can have px as suffix
            if (windowWidth <= 0) {
                windowWidth = _Utils$.format_number_int(parseInt($(window).width()) - 40, false); // $(window).width() have px as suffix
            } //end if
            if (windowWidth < 200) {
                windowWidth = 200;
            } //end if
            //--
            return windowWidth;
            //--
        }; //END
        // no export


        const getWindowHeight = (windowHeight) => {
            //--
            windowHeight = _Utils$.format_number_int(parseInt(windowHeight), false); // can have px as suffix
            if (windowHeight <= 0) {
                windowHeight = _Utils$.format_number_int(parseInt($(window).height()) - 20, false); // $(window).height() have px as suffix
            } //end if
            if (windowHeight < 100) {
                windowHeight = 100;
            } //end if
            //--
            return windowHeight;
            //--
        }; //END
        // no export


        const executeUnload = function() {
            //--
            $('#' + iFBoxBackground).unbind('click');
            $('#' + iFBoxBtnClose).unbind('click');
            $('#' + iFBoxLoader).empty().html('');
            //--
            let the_align_left = 'auto';
            let the_align_right = 'auto';
            if (param_CloseAlign === 'left') {
                the_align_left = '0px'; // left
            } else { // right
                the_align_right = '0px'; // right
            } //end if else
            //--
            $('#' + iFBoxBtnClose).css({
                'position': 'absolute',
                'width': '1px',
                'height': '1px',
                'left': the_align_left,
                'right': the_align_right,
                'top': '0px',
            }).empty().html('').hide();
            //--
            $('#' + iFBoxName).css({
                'width': '1px',
                'height': '1px'
            });
            if (param_LoaderBlank) {
                $('#' + iFBoxName).attr('src', _Utils$.escape_html(param_LoaderBlank)); // force unload
            } //end if
            $('#' + iFBoxName).attr('src', '').hide();
            //--
            $('#' + iFBoxDiv).css({
                'position': 'absolute',
                'width': '1px',
                'height': '1px',
                'left': '0px',
                'top': '0px'
            }).hide();
            //-- restore parent scrolling
            $('body').css({
                'overflow': 'auto' // need to be 'auto' instead 'visible' to work with IE
            });
            //--
            iFBoxStatus = '';
            //--
            return false;
            //--
        }; //END
        // no export


        const calculatePosition = function(windowWidth, windowHeight) {
            //--
            let the_h_align = _Utils$.format_number_int(parseInt($(window).scrollLeft()) + ((parseInt($(window).width()) - windowWidth) / 2)) + 'px';
            let the_v_align = _Utils$.format_number_int(parseInt($(window).scrollTop()) + 10) + 'px';
            if ((param_vAlign === 'center') || (param_vAlign === 'middle')) {
                the_v_align = _Utils$.format_number_int((parseInt($(window).scrollTop()) + ((parseInt($(window).height()) - windowHeight) / 2))) + 'px';
            } //end if else
            //--
            $('#' + iFBoxDiv).css({
                'position': 'absolute',
                'z-index': 2111111098, //9999998,
                'text-align': 'center',
                'left': the_h_align,
                'top': the_v_align,
                'width': windowWidth + 'px',
                'height': windowHeight + 'px'
            }).show();
            //--
        }; //END
        // no export


        const executePositioning = function(yProtect, windowWidth, windowHeight) {
            //--
            let the_wWidth = getWindowWidth(windowWidth);
            let the_wHeight = getWindowHeight(windowHeight);
            //--
            const the_wRealWidth = getWindowWidth(0);
            if (the_wRealWidth < windowWidth) {
                the_wWidth = the_wRealWidth;
            } //end if
            const the_wRealHeight = getWindowHeight(0);
            if (the_wRealHeight < windowHeight) {
                the_wHeight = the_wRealHeight;
            } //end if
            //--
            let the_style_cursor = 'auto';
            if (yProtect != 1) {
                the_style_cursor = 'pointer';
            } //end if
            $('#' + iFBoxBackground).css({
                'position': 'fixed',
                'z-index': 2111111097, //9999997,
                'cursor': the_style_cursor,
                'text-align': 'center',
                'left': '0px',
                'top': '0px',
                'width': '100%',
                'height': '100%',
            }).show();
            //--
            calculatePosition(the_wWidth, the_wHeight);
            //--
            return false;
            //--
        }; //END
        // no export


        //================================== # [EXTERNAL EVENT HANDLERS AND DOM REGISTERS]


        $(() => {
            //--
            $('body').append('<!-- SmartJS.Modal.Loader :: Start --><div id="' + _Utils$.escape_html(iFBoxBackground) + '" data-info-smartframework="SmartFramework.Js.ModalBox: ' + _Utils$.escape_html(VER) + '" style="background-color:' + _Utils$.escape_html(param_CssOverlayBgColor) + '; position:absolute; top:0px; left:0px; width:1px; height:1px; opacity: ' + _Utils$.escape_html(_Utils$.format_number_dec(param_CssOverlayOpacity, 2, false, true)) + ';"></div><div id="' + _Utils$.escape_html(iFBoxDiv) + '" style="position:absolute; top:0px; left:0px; width:1px; height:1px;"><center><div id="' + _Utils$.escape_html(iFBoxLoader) + '"></div></center><div id="' + _Utils$.escape_html(iFBoxBtnClose) + '" title="[X]"></div><iframe name="' + _Utils$.escape_html(iFBoxName) + '" id="' + _Utils$.escape_html(iFBoxName) + '" width="1" height="1" scrolling="auto" src="" marginwidth="5" marginheight="5" hspace="0" vspace="0" frameborder="0"></iframe></div><!-- END: SmartJS.Modal.Loader -->');
            //--
            initialize();
            //--
            $(window).on('resize scroll', (ev) => {
                if (getStatus() === 'visible') {
                    //_p$.log(_N$, 'Resizing the ModalBox by Window event: Resize or Scroll');
                    executePositioning(param_UseProtection, iFBoxWidth, iFBoxHeight);
                } //end if
            });
            //--
        }); // end on document ready


        //==================================


    }
}; //END CLASS

smartJ$ModalBox.secureClass(); // implements class security

window.smartJ$ModalBox = smartJ$ModalBox; // global export

//==================================================================
//==================================================================

// #END

// ===== browser_check.js

// [LIB - Smart.Framework / JS / Browser Check]
// (c) 2006-2022 unix-world.org - all rights reserved
// r.8.7 / smart.framework.v.8.7

// DEPENDS: -

//==================================================================
//==================================================================

//================== [ES6]

/**
 * CLASS :: Smart BrowserTest (ES6)
 *
 * @package Sf.Javascript:Browser
 *
 * @desc The class provide a Browser Compliance Check for JavaScript
 * @author unix-world.org
 * @license BSD
 * @file browser_check.js
 * @version 20220730
 * @class smartJ$TestBrowser
 * @static
 * @frozen
 *
 */
const smartJ$TestBrowser = new class {
    constructor() { // STATIC CLASS
        const _N$ = 'smartJ$TestBrowser';

        // :: static
        const _C$ = this; // self referencing

        const _p$ = console;

        let SECURED = false;
        _C$.secureClass = () => { // implements class security
            if (SECURED === true) {
                _p$.warn(_N$, 'Class is already SECURED');
            } else {
                SECURED = true;
                Object.freeze(_C$);
            } //end if
        }; //END

        const _Option$ = ((typeof(smartJ$Options) != 'undefined') && smartJ$Options && (typeof(smartJ$Options) === 'object') && (smartJ$Options.BrowserTest != undefined) && (typeof(smartJ$Options.BrowserTest) === 'object')) ? smartJ$Options.BrowserTest : null;

        //==

        /**
         * If set to 'yes' or 'no' will disable the javascript detection for checkIsMobileDevice() which will return always TRUE if set to 'yes' and always FALSE if set to 'no'
         * This can be used to detect mobile devices by the backend (PHP) and skip the detection from javascript side
         *
         * @default 'auto'
         * @const {String} param_isMobile
         * @set [before] smartJ$Options.BrowserTest.isMobile
         * @get N/A
         * @static
         * @memberof smartJ$TestBrowser
         */
        const param_isMobile = (_Option$ && (_Option$.isMobile === 'yes')) ? 'yes' : ((_Option$ && (_Option$.isMobile === 'no')) ? 'no' : 'auto');

        //==


        let _w$ = (typeof(window) != 'undefined') ? window : null;
        let _n$ = (typeof(navigator) != 'undefined') ? navigator : null;


        /**
         * Detect if the Browser is on a mobile device.
         * @hint It is a very basic but effective and quick detection
         *
         * @param 	{Boolean} 	skipFallbackOnscreenSize 		*Optional* Default is FALSE ; If set to TRUE will skip the fallback on screensize which may be innacurate
         *
         * @memberof smartJ$TestBrowser
         * @method checkIsMobileDevice
         * @static
         *
         * @returns {Boolean} will return TRUE if Browser seems to be a Mobile Devices, FALSE if not
         */
        const checkIsMobileDevice = function(skipFallbackOnscreenSize = false) {
            //--
            if (param_isMobile === 'yes') {
                return true; // bool
            } else if (param_isMobile === 'no') {
                return false; // bool
            } //end if
            //--
            let isMobile = false;
            if (
                _w$ && // any can be null, but if have it, consider is mobile
                ((_w$.ontouchstart !== undefined) || (_w$.orientation !== undefined))
            ) {
                isMobile = true;
            } else if (
                _w$ &&
                _w$.screen &&
                ((_w$.screen.width != undefined) && (_w$.screen.width > 0) && (_w$.screen.width < 768)) &&
                ((_w$.screen.height != undefined) && (_w$.screen.height > 0) && (_w$.screen.height < 768))
            ) {
                if (skipFallbackOnscreenSize !== true) {
                    isMobile = true;
                } //end if
            } //end if
            //--
            return !!isMobile; // bool
            //--
        }; //END
        _C$.checkIsMobileDevice = checkIsMobileDevice; // export


        /**
         * Detect if a Browser support Cookies or does not have the Cookies disabled or even may not support cookies.
         * @hint If a browser show that does not supports Cookies may be a situation like user disabled cookies in the browser or is a really unusual or old browser
         *
         * @example
         * if(!smartJ$TestBrowser.checkCookies()) {
         * 		alert('NOTICE: Your browser does not support Cookies !');
         * }
         *
         * @memberof smartJ$TestBrowser
         * @method checkCookies
         * @static
         * @arrow
         *
         * @returns {Boolean} will return TRUE if Browser supports Cookies and cookies are enabled, FALSE if not or cookies are disabled
         */
        const checkCookies = () => {
            //--
            if (_n$ && (_n$.cookieEnabled === true)) {
                return true;
            } //end if
            //--
            return false;
            //--
        }; //END
        _C$.checkCookies = checkCookies; // export


    }
}; //END CLASS

smartJ$TestBrowser.secureClass(); // implements class security

window.smartJ$TestBrowser = smartJ$TestBrowser; // global export

//==================================================================
//==================================================================

// #END

// ===== browser_utils.js

// [LIB - Smart.Framework / JS / Browser Utils]
// (c) 2006-2022 unix-world.org - all rights reserved
// r.8.7 / smart.framework.v.8.7

// DEPENDS: jQuery, smartJ$Utils, smartJ$Date, smartJ$Base64, smartJ$CryptoHash, smartJ$CryptoBlowfish, smartJ$TestBrowser
// DEPENDS-OPTIONAL: smartJ$ModalBox, jQuery.gritter, jQuery.toastr, jQuery.alertable, SmartSimpleDialog, smartJ$UI

//==================================================================
//==================================================================

//================== [ES6]

/**
 * CLASS :: Smart BrowserUtils (ES6)
 *
 * @package Sf.Javascript:Browser
 *
 * @requires		smartJ$Utils
 * @requires		smartJ$Date
 * @requires		smartJ$Base64
 * @requires		smartJ$CryptoHash
 * @requires		smartJ$CryptoBlowfish
 * @requires		smartJ$TestBrowser
 * @requires		jQuery
 * @requires		*smartJ$ModalBox
 * @requires		*jQuery.gritter
 * @requires		*jQuery.toastr
 * @requires		*jQuery.alertable
 * @requires		*SmartSimpleDialog
 * @requires		*smartJ$UI
 *
 * @desc The JavaScript class provides methods to simplify the interraction with the Browser, Ajax XHR Requests, Forms, Message Alerts and Message Dialogs, Growl and provide many useful methods for browser interraction.
 * @author unix-world.org
 * @license BSD
 * @file browser_utils.js
 * @version 20221208
 * @class smartJ$Browser
 * @static
 * @frozen
 *
 */
const smartJ$Browser = new class {
    constructor() { // STATIC CLASS
        const _N$ = 'smartJ$Browser';

        // :: static
        const _C$ = this; // self referencing

        const _p$ = console;

        let SECURED = false;
        _C$.secureClass = () => { // implements class security
            if (SECURED === true) {
                _p$.warn(_N$, 'Class is already SECURED');
            } else {
                SECURED = true;
                Object.freeze(_C$);
            } //end if
        }; //END

        const $ = jQuery; // jQuery referencing

        const _Option$ = ((typeof(smartJ$Options) != 'undefined') && smartJ$Options && (typeof(smartJ$Options) === 'object') && (smartJ$Options.BrowserUtils != undefined) && (typeof(smartJ$Options.BrowserUtils) === 'object')) ? smartJ$Options.BrowserUtils : null;

        const _Utils$ = smartJ$Utils;
        const _Date$ = smartJ$Date;
        const _Ba$e64 = smartJ$Base64;
        const _Crypto$Hash = smartJ$CryptoHash;
        const _Crypto$Blowfish = smartJ$CryptoBlowfish;
        const _Te$tBrowser = smartJ$TestBrowser;

        //== params (options)

        /**
         * Default Language ID (must be set as in PHP, see the SMART_FRAMEWORK_DEFAULT_LANG)
         * @default 'en'
         * @var {String} param_LanguageId
         * @set [before] smartJ$Options.BrowserUtils.LanguageId
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_LanguageId = (_Option$ && (typeof(_Option$.LanguageId) == 'string') && _Option$.LanguageId) ? _Utils$.stringTrim(_Option$.LanguageId) : 'en';

        /**
         * Character Set (must be set as in PHP, see the SMART_FRAMEWORK_CHARSET)
         * @default 'UTF-8'
         * @var {String} param_Charset
         * @set [before] smartJ$Options.BrowserUtils.Charset
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_Charset = (_Option$ && (typeof(_Option$.Charset) == 'string') && _Option$.Charset) ? _Utils$.stringTrim(_Option$.Charset) : 'UTF-8';

        /**
         * Cookie Domain (must be set as in PHP, see the SMART_FRAMEWORK_UNIQUE_ID_COOKIE_LIFETIME)
         * @default 0
         * @var {Integer} param_CookieLifeTime
         * @set [before] smartJ$Options.BrowserUtils.CookieLifeTime
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_CookieLifeTime = (_Option$ && (typeof(_Option$.CookieLifeTime) == 'number') && _Option$.CookieLifeTime && _Utils$.isFiniteNumber(_Option$.CookieLifeTime)) ? _Utils$.format_number_int(_Option$.CookieLifeTime, false) : 0;

        /**
         * Cookie Domain (must be set as in PHP, see the SMART_FRAMEWORK_UNIQUE_ID_COOKIE_DOMAIN)
         * @default ''
         * @var {String} param_CookieDomain
         * @set [before] smartJ$Options.BrowserUtils.CookieDomain
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_CookieDomain = (_Option$ && (typeof(_Option$.CookieDomain) == 'string') && _Option$.CookieDomain) ? _Utils$.stringTrim(_Option$.CookieDomain) : '';

        /**
         * Cookie SameSite Policy (must be set as in PHP, see the SMART_FRAMEWORK_UNIQUE_ID_COOKIE_SAMESITE)
         * @default ''
         * @var {String} param_CookieSameSitePolicy
         * @set [before] smartJ$Options.BrowserUtils.CookieSameSitePolicy
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_CookieSameSitePolicy = (_Option$ && (typeof(_Option$.CookieSameSitePolicy) == 'string') && _Option$.CookieSameSitePolicy) ? _Utils$.stringTrim(_Option$.CookieSameSitePolicy) : '';

        /**
         * Notification Mode
         * Allowed Values: 'growl' | 'dialog'
         * @default 'growl'
         * @var {String} param_Notifications
         * @set [before] smartJ$Options.BrowserUtils.Notifications
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_Notifications = (_Option$ && (typeof(_Option$.Notifications) == 'string') && _Option$.Notifications) ? _Utils$.stringTrim(_Option$.Notifications) : 'growl';

        /**
         * Growl Notification Dialog Type
         * Allowed Values: 'auto' | 'native' | 'alertable' | 'dialog' | 'ui' ; auto=autoselect (reverse order, starting from ui to 'native' with fallback) ; native = browser:alert/prompt ; alertable = use jQuery.alertable ; dialog = use SmartSimpleDialog ; ui = use smartJ$UI
         * @default 'auto'
         * @var {String} param_NotificationDialogType
         * @set [before] smartJ$Options.BrowserUtils.NotificationDialogType
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_NotificationDialogType = (_Option$ && (typeof(_Option$.NotificationDialogType) == 'string') && _Option$.NotificationDialogType) ? _Utils$.stringTrim(_Option$.NotificationDialogType) : 'auto';

        /**
         * Growl Notification Growl Type
         * Allowed Values: 'auto' | 'gritter' | 'toastr' ; If Explicit set on 'gritter' and gritter is n/a will fallback to alert ; If Explicit set on 'toastr' and toastr is n/a will fallback to alert
         * @default 'auto'
         * @var {String} param_NotificationGrowlType
         * @set [before] smartJ$Options.BrowserUtils.NotificationGrowlType
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_NotificationGrowlType = (_Option$ && (typeof(_Option$.NotificationGrowlType) == 'string') && _Option$.NotificationGrowlType) ? _Utils$.stringTrim(_Option$.NotificationGrowlType) : 'auto';

        /**
         * Growl Notification Time when OK (in microseconds)
         * @default 1000
         * @var {Integer} param_NotificationTimeOK
         * @set [before] smartJ$Options.BrowserUtils.NotificationTimeOK
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_NotificationTimeOK = (_Option$ && (typeof(_Option$.NotificationTimeOK) == 'number') && _Option$.NotificationTimeOK && _Utils$.isFiniteNumber(_Option$.NotificationTimeOK)) ? _Utils$.format_number_int(_Option$.NotificationTimeOK, false) : 1000;

        /**
         * Growl Notification Time when ERR (in microseconds)
         * @default 3500
         * @var {Integer} param_NotificationTimeERR
         * @set [before] smartJ$Options.BrowserUtils.NotificationTimeERR
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_NotificationTimeERR = (_Option$ && (typeof(_Option$.NotificationTimeERR) == 'number') && _Option$.NotificationTimeERR && _Utils$.isFiniteNumber(_Option$.NotificationTimeERR)) ? _Utils$.format_number_int(_Option$.NotificationTimeERR, false) : 3500;

        /**
         * Errors Notification Mode
         * If set to FALSE will not raise notifications on errors but only will log them
         * Allowed Values: true | false
         * @default false
         * @var {Boolean} param_NotifyLoadError
         * @set [before] smartJ$Options.BrowserUtils.NotifyLoadError
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_NotifyLoadError = (_Option$ && (!!_Option$.NotifyLoadError)) ? true : false;

        /**
         * Use ModalBox
         * If set to 0 will disable the modal iframe ; If set to 1 (as default) will use ModalBox except on mobiles ; If set to 2 will use ModalBox also on mobiles
         * Allowed Values: 2=true (includding mobile) | 1=true (except on mobile) | 0=false
         * @default 1
         * @var {Boolean} param_ModalBoxActive
         * @set [before] smartJ$Options.BrowserUtils.ModalBoxActive
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_ModalBoxActive = (_Option$ && (_Option$.ModalBoxActive === 2)) ? 2 : ((_Option$ && (_Option$.ModalBoxActive === 0)) ? 0 : 1);

        /**
         * Enable or Disable the ModalBox Cascading
         * If set to FALSE will enable the ModalBox (iframe) cascading (inefficient) ; otherwise will use PopUp every next level starting from the ModalBox iframe level, but inside PopUp can open another ModalBox ... and so on
         * Allowed Values: true | false
         * @default true
         * @var {Boolean} param_ModalBoxNoCascade
         * @set [before] smartJ$Options.BrowserUtils.ModalBoxNoCascade
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_ModalBoxNoCascade = (_Option$ && (_Option$.ModalBoxNoCascade === false)) ? false : true;

        /**
         * Set ModalBox protected mode (if used)
         * If set to TRUE will use the protected mode for the modal iFrame (can be closed just explicit by buttons, not clicking outside of it)
         * Allowed Values: true | false
         * @default false
         * @var {Boolean} param_ModalBoxProtected
         * @set [before] smartJ$Options.BrowserUtils.ModalBoxProtected
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_ModalBoxProtected = (_Option$ && (!!_Option$.ModalBoxProtected)) ? true : false;

        /**
         * Loader Image used to display in various contexts when loading ...
         * @default 'lib/js/framework/img/loading.svg'
         * @var {String} param_LoaderImg
         * @set [before] smartJ$Options.BrowserUtils.LoaderImg
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_LoaderImg = (_Option$ && (typeof(_Option$.LoaderImg) == 'string') && _Option$.LoaderImg) ? _Utils$.stringTrim(_Option$.LoaderImg) : 'lib/js/framework/img/loading.svg';

        /**
         * Loader Blank HTML Page used to clear and free memory before loading or after unloading ... (ModalBox / PopUp)
         * @default 'lib/js/framework/loading.html'
         * @var {String} param_LoaderBlank
         * @set [before] smartJ$Options.BrowserUtils.LoaderBlank
         * @static
         * @private
         * @memberof smartJ$Browser
         */
        _C$.param_LoaderBlank = (_Option$ && (typeof(_Option$.LoaderBlank) == 'string') && _Option$.LoaderBlank) ? _Utils$.stringTrim(_Option$.LoaderBlank) : 'lib/js/framework/loading.html';

        /**
         * OK sign image, used in various contexts
         * @default 'lib/framework/img/sign-ok.svg'
         * @var {String} param_ImgOK
         * @set [before] smartJ$Options.BrowserUtils.ImgOK
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_ImgOK = (_Option$ && (typeof(_Option$.ImgOK) == 'string') && _Option$.ImgOK) ? _Utils$.stringTrim(_Option$.ImgOK) : 'lib/framework/img/sign-ok.svg';

        /**
         * Not OK sign image, used in various contexts
         * @default 'lib/framework/img/sign-warn.svg'
         * @var {String} param_ImgNotOK
         * @set [before] smartJ$Options.BrowserUtils.ImgNotOK
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_ImgNotOK = (_Option$ && (typeof(_Option$.ImgNotOK) == 'string') && _Option$.ImgNotOK) ? _Utils$.stringTrim(_Option$.ImgNotOK) : 'lib/framework/img/sign-warn.svg';

        /**
         * Clone Add (Insert), used for the cloning HTML elements context by CloneElement()
         * @default 'lib/js/framework/img/clone-insert.svg'
         * @var {String} param_ImgCloneInsert
         * @set [before] smartJ$Options.BrowserUtils.ImgCloneInsert
         * @static
         * @private
         * @memberof smartJ$Browser
         */
        _C$.param_ImgCloneInsert = (_Option$ && (typeof(_Option$.ImgCloneInsert) == 'string') && _Option$.ImgCloneInsert) ? _Utils$.stringTrim(_Option$.ImgCloneInsert) : 'lib/js/framework/img/clone-insert.svg';

        /**
         * Clone Remove (Delete), used for the cloning HTML elements context by CloneElement()
         * @default 'lib/js/framework/img/clone-remove.svg'
         * @var {String} param_ImgCloneRemove
         * @set [before] smartJ$Options.BrowserUtils.ImgCloneRemove
         * @static
         * @private
         * @memberof smartJ$Browser
         */
        _C$.param_ImgCloneRemove = (_Option$ && (typeof(_Option$.ImgCloneRemove) == 'string') && _Option$.ImgCloneRemove) ? _Utils$.stringTrim(_Option$.ImgCloneRemove) : 'lib/js/framework/img/clone-remove.svg'; // private, used by CloneElement()

        /**
         * Maximize / Unmaximize HTML element, used by createMaxContainer()
         * @default 'lib/js/framework/img/fullscreen-on.svg'
         * @var {String} param_IconImgFullScreen
         * @set [before] smartJ$Options.BrowserUtils.IconImgFullScreen
         * @static
         * @private
         * @memberof smartJ$Browser
         */
        _C$.param_IconImgFullScreen = (_Option$ && (typeof(_Option$.IconImgFullScreen) == 'string') && _Option$.IconImgFullScreen) ? _Utils$.stringTrim(_Option$.IconImgFullScreen) : 'lib/js/framework/img/fullscreen-on.svg'; // private, used by createMaxContainer()

        /**
         * Overlay Customization - Background Color
         * Allowed Values: hexa color between #000000 - #FFFFFF
         * @default '#FFFFFF'
         * @var {String} param_CssOverlayBgColor
         * @set [before] smartJ$Options.BrowserUtils.CssOverlayBgColor
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_CssOverlayBgColor = (_Option$ && (typeof(_Option$.CssOverlayBgColor) == 'string') && _Option$.CssOverlayBgColor && String(_Option$.CssOverlayBgColor).match(/^\#([0-9a-f]{6})$/i)) ? _Utils$.stringTrim(_Option$.CssOverlayBgColor) : '#FFFFFF';

        /**
         * Overlay Customization - Opacity
         * Allowed Values: between 0.1 and 1
         * @default 0.85
         * @var {Decimal} param_CssOverlayOpacity
         * @set [before] smartJ$Options.BrowserUtils.CssOverlayOpacity
         * @static
         * @memberof smartJ$Browser
         */
        _C$.param_CssOverlayOpacity = (_Option$ && (typeof(_Option$.CssOverlayOpacity) == 'number') && _Utils$.isFiniteNumber(_Option$.CssOverlayOpacity) && (_Utils$.format_number_float(_Option$.CssOverlayOpacity, false) >= 0) && (_Utils$.format_number_float(_Option$.CssOverlayOpacity, false) <= 1)) ? _Utils$.format_number_dec(_Option$.CssOverlayOpacity, 2, false, true) : 0.85;


        /**
         * The time in microseconds for delayed close of PopUp or Modal
         * @default 750
         * @var {Integer} param_TimeDelayCloseWnd
         * @set [before] smartJ$Options.BrowserUtils.TimeDelayCloseWnd
         * @static
         * @private
         * @memberof smartJ$Browser
         */
        _C$.param_TimeDelayCloseWnd = (_Option$ && (typeof(_Option$.TimeDelayCloseWnd) == 'number') && _Option$.TimeDelayCloseWnd && _Utils$.isFiniteNumber(_Option$.TimeDelayCloseWnd)) ? _Utils$.format_number_int(_Option$.TimeDelayCloseWnd, false) : 750;

        //==

        //-- specials: don't change them ...
        let flag_PageUnloadConfirm = false; // keeps the status of PageUnloadConfirm ; default is false
        let flag_PageAway = false; // keeps the status of PageAway handler ; default is false
        let flag_RefreshState = 0; // if=1, will refresh parent ; default is 0
        let flag_RefreshURL = ''; // ^=1 -> if != '' will redirect parent ; default is ''
        //-- Debug stuff
        let flag_DebugEnabled = false; // needed for some javascript actions to confirm when leaving the page
        let flag_DebugPageAway = false; // keeps the status of PageAway handler ; default is false
        //--

        //==

        //--
        const defSmartPopupTarget = 'smartPWin'; // the Default SmartPopUp Target Name ; will be used when no target is passed
        //--
        let objRefWinPopup = null; // null or holds the pop-up window reference to avoid opening new popups each time ; reuse it if exists and just focus it (identified by window.name / target.name)
        //--

        //==

        //--
        const objKeyEntries = '_Crypto$Hash.crc32b({' + _Utils$.stringUcFirst(String(typeof(_C$)).toLowerCase()) + '.entries(' + _N$ + '[!is' + _Utils$.create_jsvar('{' + +'}') + '()]' + ')}.to' + _Utils$.stringUcFirst(typeof([] + [])) + '())';
        //--
        const sfOverlayID = 'smart-framework-overlay';
        //--
        const cssAlertable = 'background:#555555; color:#FFFFFF; font-size:1.5rem; font-weight:bold; text-align:right; padding-top:3px; padding-left:10px; padding-right:10px; margin-bottom:20px;';
        //--
        const regexValidCookieName = RegExp(/^[a-zA-Z0-9_\-]+$/); // {{{SYNC-REGEX-URL-VARNAME}}}
        //--
        const HTTP_STATUS_CODES = {
            '200': 'OK',
            '202': 'Accepted',
            '203': 'Non-Authoritative Information',
            '208': 'Already Reported',
            '301': 'Moved Permanently',
            '302': 'Found',
            '304': 'Not Modified',
            '400': 'Bad Request',
            '401': 'Unauthorized',
            '403': 'Forbidden',
            '404': 'Not Found',
            '429': 'Too Many Requests',
            '500': 'Internal Server Error',
            '502': 'Bad Gateway',
            '503': 'Service Unavailable',
            '504': 'Gateway Timeout',
        };
        //--

        //==


        /**
         * Set an internal flag
         * Available flags are:
         * 		PageUnloadConfirm 	{Boolean}
         * 		PageAway 			{Boolean}
         * 		RefreshState 		{0/1}
         * 		RefreshURL 			{String} || ''
         *
         * @memberof smartJ$Browser
         * @method setFlag
         * @static
         * @arrow
         *
         * @param 	{String} 	flag 		The flag to set
         * @param 	{Mixed} 	value 		The value of that flag
         *
         * @return 	{Boolean} 				TRUE if success, FALSE if not
         */
        const setFlag = (flag, value) => { // ES6
            //--
            flag = String(flag || '');
            //--
            switch (flag) {
                case 'PageUnloadConfirm':
                    flag_PageUnloadConfirm = !!value; // boolean
                    return true;
                    break;
                case 'PageAway':
                    if (flag_DebugEnabled === true) {
                        _p$.log(_N$, 'Debug is ON, the setFlag:PageAway is overriden ...');
                        flag_DebugPageAway = !!value; // boolean, store in a separate place, need for restore
                    } else {
                        flag_PageAway = !!value; // boolean
                    } //end if
                    return true;
                    break;
                case 'RefreshState':
                    flag_RefreshState = (!!value ? 1 : 0); // 0/1
                    return true;
                    break;
                case 'RefreshURL':
                    flag_RefreshURL = _Utils$.stringPureVal(value, true); // cast to string, trim ; String: '%url%' || ''
                    return true;
                    break;
                case 'DebugEnabled':
                    flag_DebugEnabled = !!value; // boolean
                    if (flag_DebugEnabled === true) {
                        PageAwayControl('Debug is ON. Confirm Leaving the page. This confirmation is to avoid javascript redirects without explicit confirmation when Debug is enabled.');
                    } else {
                        flag_PageAway = !!flag_DebugPageAway; // restore
                    } //end if else
                    return true;
                    break;
                default: // N/A
                    _p$.warn(_N$, 'Set Invalid Flag:', flag, value);
            } //end switch
            //--
            return false;
            //--
        }; //END
        _C$.setFlag = setFlag; // export


        /**
         * Get an internal flag
         * Available flags are:
         * 		PageUnloadConfirm 	{Boolean}
         * 		PageAway 			{Boolean}
         * 		RefreshState 		{0/1}
         * 		RefreshURL 			{String} || ''
         *
         * @memberof smartJ$Browser
         * @method getFlag
         * @static
         * @arrow
         *
         * @param 	{String} 	flag 		The flag to return
         *
         * @return 	{Mixed} 				The value of that flag
         */
        const getFlag = (flag) => { // ES6
            //--
            flag = String(flag || '');
            //--
            switch (flag) {
                case 'PageUnloadConfirm':
                    return !!flag_PageUnloadConfirm; // bool
                    break;
                case 'PageAway':
                    return !!flag_PageAway; // bool
                    break;
                case 'RefreshState':
                    return (flag_RefreshState ? 1 : 0); // 0/1
                    break;
                case 'RefreshURL':
                    return _Utils$.stringPureVal(flag_RefreshURL, true); // cast to string, trim ; String: '%url%' || ''
                    break;
                case 'DebugEnabled':
                    return !!flag_DebugEnabled; // bool
                    break;
                default: // N/A
                    _p$.warn(_N$, 'Get Invalid Flag:', flag);
            } //end switch
            //--
            return null;
            //--
        }; //END
        _C$.getFlag = getFlag; // export


        /**
         * Get the popUp Window Object Reference if any
         *
         * @memberof smartJ$Browser
         * @method getRefPopup
         * @static
         * @arrow
         *
         * @return 	{Mixed} 				NULL or Object (PopUp Window Reference)
         */
        const getRefPopup = () => objRefWinPopup; // ES6
        _C$.getRefPopup = getRefPopup; // export


        /**
         * Get the Current ISO Date and Time
         *
         * @memberof smartJ$Browser
         * @method getCurrentIsoDateTime
         * @static
         * @arrow
         *
         * @return 	{String} 				The Current ISO Date and Time as YYYY-MM-DD HH:II:SS
         */
        const getCurrentIsoDateTime = () => { // ES6
            //--
            const crrDate = new Date();
            //--
            return String(_Date$.getIsoDate(crrDate, true));
            //--
        }; // END
        _C$.getCurrentIsoDateTime = getCurrentIsoDateTime; // export


        /**
         * Blowfish (CBC) Encrypt
         *
         * @memberof smartJ$Browser
         * @method blowfishEncrypt
         * @static
         * @arrow
         *
         * @param 	{String} 	str 		The plain string to be encoded
         *
         * @return 	{String} 				The Blowfish Encrypted string
         */
        const blowfishEncrypt = (str) => { // ES6
            //--
            str = _Utils$.stringPureVal(str); // cast to string, don't trim ! need to preserve the value
            if (str == '') {
                return '';
            } //end if
            //--
            return String(_Crypto$Blowfish.encrypt(str, objKeyEntries + '(' + "'" + _Crypto$Hash.crc32b(_N$) + "'" + ');'));
            //--
        }; // END
        _C$.blowfishEncrypt = blowfishEncrypt; // export


        /**
         * Blowfish (CBC) Decrypt
         *
         * @memberof smartJ$Browser
         * @method blowfishDecrypt
         * @static
         * @arrow
         *
         * @param 	{String} 	enc 		The Blowfish Encrypted string
         *
         * @return 	{String} 				The plain decoded string
         */
        const blowfishDecrypt = (enc) => { // ES6
            //--
            enc = _Utils$.stringPureVal(enc, true); // cast to string, trim
            if (enc == '') {
                return '';
            } //end if
            //--
            return String(_Crypto$Blowfish.decrypt(enc, objKeyEntries + '(' + "'" + _Crypto$Hash.crc32b(_N$) + "'" + ');'));
            //--
        }; // END
        _C$.blowfishDecrypt = blowfishDecrypt; // export


        /**
         * Strip HTML Tags and return plain text from HTML Code
         *
         * @memberof smartJ$Browser
         * @method stripTags
         * @static
         * @arrow
         *
         * @param 	{String} 	html 		The html code to be processed
         *
         * @return 	{String} 				Plain Text
         */
        const stripTags = (html) => { // ES6
            //--
            html = _Utils$.stringPureVal(html); // cast to string, don't trim ! need to preserve the value
            if (html == '') {
                return '';
            } //end if
            //--
            return $('<div>' + html.replace(/(<([^>]+)>)/g, ' ') + '</div>').text();
            //--
        }; // END
        _C$.stripTags = stripTags; // export


        /**
         * Parse Current URL to extract GET Params
         * @example 	'http(s)://some.url/?param1=value1&param2=value%202' // sample URL
         *
         * @memberof smartJ$Browser
         * @method parseCurrentUrlGetParams
         * @static
         *
         * @param 	{Boolean} 	semantic_sf_url 		*Optional* ; Default is TRUE ; If set to FALSE will not post-process the semantic URL parts from Smart.Framework such as: `/page/one/something/else`
         *
         * @return 	{Object} 							{ param1:'value1', param1:'value 2', ... }
         */
        const parseCurrentUrlGetParams = function(semantic_sf_url = true) { // ES6
            //--
            let result = {};
            //--
            if (!location.search) {
                return result; // Object
            } //end if
            let query = String(location.search.substr(1)); // 'param1=value1&param2=value%202' from '?param1=value1&param2=value%202'
            if (!query) {
                return result; // Object
            } //end if
            //--
            query.split('&').forEach((part) => {
                let item = '';
                let v = '';
                let s = '';
                part = String(part);
                if (part) {
                    item = part.split('=');
                    v = String(item[0]);
                    if ((semantic_sf_url === true) && (v.indexOf('/') !== -1)) { // process or not semantic url from sf
                        let arr = v.split('/');
                        let found = false;
                        for (let i = 0; i < arr.length; i++) {
                            if (found !== true) {
                                if (arr[i] !== '') {
                                    found = true; // start with 1st valid sequence
                                } else {
                                    continue;
                                } //end if
                            } //end if
                            result[String(arr[i])] = String(decodeURIComponent(String(arr[i + 1] ? arr[i + 1] : '')) || '');
                            i += 1;
                        } //end for
                    } else {
                        result[String(item[0])] = String(decodeURIComponent(String(item[1] ? item[1] : '')) || '');
                    } //end if else
                } //end if
            });
            //--
            return result; // Object
            //--
        }; //END
        _C$.parseCurrentUrlGetParams = parseCurrentUrlGetParams; // export


        /**
         * Print current Browser Page
         *
         * @memberof smartJ$Browser
         * @method PrintPage
         * @static
         * @arrow
         *
         * @fires Print Dialog Show Event
         */
        const PrintPage = () => { // ES6
            //--
            try {
                self.print();
            } catch (err) {
                _p$.warn(_N$, 'Print Page is N/A:', err);
                AlertDialog('NOTICE: Printing may not be available in your browser');
            } //end try catch
            //--
        }; //END
        _C$.PrintPage = PrintPage; // export


        /**
         * Count Down handler that bind to a HTML Element
         *
         * @memberof smartJ$Browser
         * @method CountDown
         * @static
         *
         * @param 	{Integer} 	counter 		The countdown counter, Min value is 1
         * @param 	{String} 	elID 			The HTML Element ID to bind to or NULL
         * @param 	{JS-Code} 	evcode 			*Optional* the JS Code to execute on countdown complete (when countdown to zero)
         * @param	{Boolean}	prettyFormat	*Optional* if set to TRUE instead to display the left time in seconds will display as pretty format like: Days, Hours, Minutes, Seconds
         * @fires 	A custom event set in the Js Code to execute when done
         */
        const CountDown = function(counter, elID, evcode, prettyFormat) { // ES6
            //--
            const _m$ = 'CountDown';
            //--
            if ((counter == undefined) || (counter == '')) { // undef tests also for null
                _p$.error(_N$, _m$, 'ERR: undefined Counter Init');
                return;
            } //end if
            //--
            counter = _Utils$.format_number_int(counter, false);
            if (counter < 1) {
                return; // avoid infinite cycle
            } //end if
            //--
            const cdwn = setInterval(() => {
                //--
                if (counter > 0) {
                    //--
                    counter = counter - 1;
                    //--
                    if ((elID != undefined) && (elID != '')) { // undef tests also for null
                        const theID = _Utils$.create_htmid(elID);
                        let cntTxt = String(counter);
                        if (prettyFormat === true) {
                            cntTxt = String(_Date$.prettySecondsToDHMS(counter));
                        } //end if
                        if (theID != '') {
                            $('#' + theID).empty().text(cntTxt);
                        } //end if
                    } //end if
                    //--
                } else {
                    //--
                    clearInterval(cdwn);
                    //--
                    _Utils$.evalJsFxCode( // EV.CTX
                        _N$ + '.' + _m$,
                        (typeof(evcode) === 'function' ?
                            () => {
                                'use strict'; // req. strict mode for security !
                                (evcode)(counter, elID);
                            } :
                            () => {
                                'use strict'; // req. strict mode for security !
                                !!evcode ? eval(evcode) : null // already is sandboxed in a method to avoid code errors if using return ; need to be evaluated in this context because of parameters access: counter, elID
                            }
                        )
                    );
                    //--
                } //end if
                //--
            }, 1000); // 1 second
            //--
        }; //END
        _C$.CountDown = CountDown; // export


        /**
         * Focus a browser window by reference
         *
         * @memberof smartJ$Browser
         * @method windowFocus
         * @static
         * @arrow
         *
         * @param 	{Object} 	wnd 		The window (reference) object ; which ? self, window, ...
         *
         * @fires activate Focus on a PopUp window
         */
        const windowFocus = (wnd) => {
            //--
            try {
                wnd.focus(); // focus the window (it may fail if window reference with parent is lost, needs try/catch)
            } catch (err) {} // older browsers have some bugs
            //--
        }; //END
        _C$.windowFocus = windowFocus; // export


        /**
         * Detect if a Browser Window is an iFrame
         *
         * @memberof smartJ$Browser
         * @method WindowIsiFrame
         * @static
         * @arrow
         *
         * @return 	{Boolean} 							TRUE if iFrame, FALSE if not
         */
        const WindowIsiFrame = () => { // ES6
            //--
            if (window.self !== window.top) {
                return true; // is iframe
            } //end if
            //--
            return false; // not an iframe
            //--
        }; //END
        _C$.WindowIsiFrame = WindowIsiFrame; // export


        /**
         * Detect if a Browser Window is a PopUp
         *
         * @memberof smartJ$Browser
         * @method WindowIsPopup
         * @static
         * @arrow
         *
         * @return 	{Boolean} 							TRUE if PopUp, FALSE if not
         */
        const WindowIsPopup = () => { // ES6
            //--
            if (window.opener) {
                return true; // is popup
            } //end if
            //--
            return false; // not an popup
            //--
        }; //END
        _C$.WindowIsPopup = WindowIsPopup; // export


        /**
         * Page Away control handler. Take control over the events as onbeforeunload to prevent leaving the browser page in unattended mode
         *
         * @memberof smartJ$Browser
         * @method PageAwayControl
         * @static
         *
         * @param 	{String} 	the_question 	The Question to confirm for navigate away of the page
         *
         * @fires Ask Confirmation Dialog to confirm navigate away of the page
         * @listens Browser Page Unload
         */
        const PageAwayControl = function(the_question) { // ES6
            //--
            _C$.setFlag('PageUnloadConfirm', true);
            //--
            the_question = _Utils$.stringPureVal(the_question, true); // cast to string, trim
            if (the_question == '') {
                the_question = 'Confirm leaving the page ... ?';
            } //end if
            //--
            $(window).on('beforeunload', (evt) => {
                let e = evt || window.event;
                if (_C$.getFlag('PageAway') != true) {
                    e.preventDefault();
                    if (_C$.getFlag('DebugEnabled') == true) {
                        const debugMsgTitle = '*** DEBUG IS ON ***';
                        const debugMsgText = 'Page Leave Confirmation is ENABLED';
                        _p$.log(_N$, debugMsgTitle, debugMsgText);
                        if (GrowlSelectType() != '') {
                            GrowlNotificationAdd(_Utils$.escape_html(debugMsgTitle), _Utils$.escape_html(debugMsgText), '', 10000, false, 'yellow');
                        } //end if
                    } //end if
                    return String(the_question);
                } //end if
            }).on('unload', () => {
                _C$.setFlag('PageAway', true);
            });
            //--
            if (WindowIsiFrame() === true) { // try to set only if iframe
                if (typeof(parent.smartJ$ModalBox) != 'undefined') {
                    try {
                        parent.smartJ$ModalBox.setHandlerOnBeforeUnload(() => {
                            if (_C$.getFlag('PageAway') != true) {
                                let is_exit = confirm(String(the_question)); // true or false
                                if (is_exit) {
                                    _C$.setFlag('PageAway', true);
                                } //end if
                                return is_exit;
                            } else {
                                return true;
                            } //end if else
                        });
                    } catch (err) {
                        _p$.error(_N$, 'ERR: PageAwayControl failed on ModalBox:', err);
                    } //end try catch
                } //end if
            } //end if
            //--
        }; //END
        _C$.PageAwayControl = PageAwayControl; // export


        /**
         * Redirect to a new URL or Reload the current browser location
         *
         * @memberof smartJ$Browser
         * @method RedirectToURL
         * @static
         * @arrow
         *
         * @param 	{String} 	yURL 		The URL (relative or absolute) to redirect to ; if = '@' will just autorefresh the page
         * @fires Browser Location Redirect or Location Reload
         */
        const RedirectToURL = (yURL) => { // ES6
            //--
            yURL = _Utils$.stringPureVal(yURL, true); // cast to string, trim
            if (yURL == '') {
                _p$.warn(_N$, 'WARN: RedirectToURL: empty URL');
                return;
            } //end if
            //--
            setFlag('PageAway', true);
            if (yURL === '@') {
                self.location = self.location; // avoid location reload to avoid re-post vars
            } else {
                self.location = String(yURL);
            } //end if else
            //--
        }; //END
        _C$.RedirectToURL = RedirectToURL; // export


        /**
         * Delayed Redirect to URL current browser window
         *
         * @memberof smartJ$Browser
         * @method RedirectDelayedToURL
         * @static
         *
         * @param 	{String} 	yURL 		The URL (relative or absolute) to redirect to
         * @param 	{Integer} 	ytime 		The time delay in milliseconds
         *
         * @fires Redirect Browser to a new URL
         * @listens TimeOut counter on Set as Delay
         */
        const RedirectDelayedToURL = function(yURL, ytime) { // ES6
            //--
            ytime = _Utils$.format_number_int(ytime, false);
            //--
            setTimeout(() => {
                RedirectToURL(yURL);
            }, ytime);
            //--
        }; //END
        _C$.RedirectDelayedToURL = RedirectDelayedToURL; // export


        /**
         * Redirect to URL the browser parent window
         *
         * @memberof smartJ$Browser
         * @method RedirectParent
         * @static
         *
         * @param 	{String} 	yURL 			The URL (relative or absolute) to redirect to
         *
         * @fires Redirect Browser to a new URL
         */
        const RedirectParent = function(yURL) { // ES6
            //--
            const _m$ = 'RedirectParent';
            //--
            yURL = _Utils$.stringPureVal(yURL, true); // cast to string, trim
            if (yURL == '') {
                _p$.warn(_N$, _m$, 'WARN: empty URL');
                return;
            } //end if
            //--
            yURL = String(yURL);
            //--
            if (WindowIsPopup() === true) { // when called from PopUp
                try {
                    window.opener.location = yURL;
                } catch (err) {
                    _p$.error(_N$, _m$, 'ERR: PopUp:', err);
                } //end try catch
            } else if (WindowIsiFrame() === true) { // when called from iFrame
                try {
                    parent.location = yURL;
                } catch (err) {
                    _p$.error(_N$, _m$, 'ERR: iFrame:', err);
                } //end try catch
            } //end if else
            //--
        }; //END
        _C$.RedirectParent = RedirectParent;


        /**
         * Lazy Refresh parent browser window or Lazy redirect parent window to another URL.
         * @desc The method is different than RedirectParent() because will be executed just after the child (modal iFrame / PopUp) is closed.
         * @hint It will just trigger a lazy refresh on parent that will be executed later, after closing current child window.
         *
         * @memberof smartJ$Browser
         * @method RefreshParent
         * @static
         *
         * @param 	{String} 	yURL 			*Optional* The URL (relative or absolute) to redirect to ; if the parameter is not specified will just reload the parent window with the same URL
         *
         * @fires Refresh Parent Window direct (instant) / or indirect (after closing modal)
         */
        const RefreshParent = function(yURL) { // ES6
            //--
            const _m$ = 'RefreshParent';
            //--
            yURL = _Utils$.stringPureVal(yURL, true); // cast to string, trim
            //--
            if (WindowIsPopup() === true) { // when called from PopUp
                //--
                try {
                    if (window.opener) {
                        if ((typeof(window.opener.smartJ$Browser) == 'undefined') || (typeof(window.opener.smartJ$ModalBox) == 'undefined')) {
                            _p$.warn(_N$, _m$, 'ERR: N/A for PopUp Parent');
                            return;
                        } //end if
                        if (window.opener.smartJ$Browser.getFlag('PageUnloadConfirm') === true) {
                            return; // Parent Refresh skip, already have PageAway Confirmation
                        } //end if
                        if (window.opener.smartJ$ModalBox.getStatus() === 'visible') { // {{{SYNC-TRANSFER-MODAL-POPUP-REFRESH}}}
                            window.opener.smartJ$ModalBox.setRefreshParent(1, String(yURL));
                            return;
                        } //end if
                        window.opener.smartJ$Browser.setFlag('RefreshState', 1);
                        window.opener.smartJ$Browser.setFlag('RefreshURL', yURL);
                    } //end if
                } catch (err) {
                    _p$.error(_N$, _m$, 'ERR: Failed for PopUp:', err);
                } //end try catch
                //--
            } else if (WindowIsiFrame() === true) { // when called from iFrame
                //--
                try {
                    if (self.name) {
                        if ((typeof(parent.smartJ$Browser) == 'undefined') || (typeof(parent.smartJ$ModalBox) == 'undefined')) {
                            _p$.warn(_N$, _m$, 'ERR: N/A for iFrame Parent');
                            return;
                        } //end if
                        if (self.name === parent.smartJ$ModalBox.getName()) {
                            if (parent.smartJ$Browser.getFlag('PageUnloadConfirm') !== true) {
                                parent.smartJ$ModalBox.setRefreshParent(true, yURL);
                            } //end if
                        } //end if
                    } //end if
                } catch (err) {
                    _p$.error(_N$, _m$, 'ERR: Failed from iFrame:', err);
                } //end try catch
                //--
            } //end if else
            //--
        }; //END
        _C$.RefreshParent = RefreshParent; // export


        /**
         * Close a Modal / PopUp child browser window.
         * If a parent refresh is pending will execute it after.
         * @hint It is the prefered way to close a child window (modal iFrame / PopUp) using a button or just executing some js code in a child page.
         *
         * @memberof smartJ$Browser
         * @method CloseModalPopUp
         * @static
         *
         * @fires Closes a Modal Box or PopUp Window
         */
        const CloseModalPopUp = function() { // ES6
            //--
            const _m$ = 'CloseModalPopUp';
            //--
            if (WindowIsPopup() === true) { // when called from PopUp ; not necessary for normal situations as it is fired directly by close monitor {{{SYNC-POPUP-Refresh-Parent-By-EXEC}}} ; err is catched inside ; it is required only for the situation when popup is created manually with window.open NOT by using the initModalOrPopUp()
                //--
                if ((window.opener) && (typeof(window.opener.smartJ$Browser) != 'undefined')) {
                    try {
                        if ((window.opener.smartJ$Browser.getRefPopup()) && (window.opener.smartJ$Browser.getRefPopup() === self)) {
                            // situation will be handled by the initModalOrPopUp handler (timer) for PopUps, no needed to use directly
                        } else {
                            if (window.opener.smartJ$Browser.getFlag('RefreshState')) {
                                if (window.opener.smartJ$Browser.getFlag('RefreshURL')) {
                                    window.opener.location = String(window.opener.smartJ$Browser.getFlag('RefreshURL'));
                                } else {
                                    window.opener.location = window.opener.location; // FIX: avoid location reload to resend POST vars !!
                                } //end if else
                                window.opener.smartJ$Browser.setFlag('RefreshState', 0);
                                window.opener.smartJ$Browser.setFlag('RefreshURL', '');
                            } //end if
                        } //end if else
                    } catch (err) {
                        _p$.error(_N$, _m$, 'ERR: Failed with PopUp:', err);
                    } //end try catch
                } else {
                    _p$.warn(_N$, _m$, 'ERR: PopUp Parent BrowserUtils is N/A');
                } //end if else
                //--
                try {
                    self.close(); // it may fail if window reference with parent is lost (aka orphan window), needs try/catch
                } catch (err) {}
                //--
            } else if (WindowIsiFrame() === true) { // when called from iFrame
                //--
                if (typeof(parent.smartJ$ModalBox) == 'undefined') {
                    _p$.warn(_N$, _m$, 'ERR: iFrame Parent ModalBox is N/A');
                    return;
                } //end if
                //--
                try {
                    if (self.name) {
                        if (self.name === parent.smartJ$ModalBox.getName()) {
                            parent.smartJ$ModalBox.UnloadURL(); // {{{SYNC-MODAL-Refresh-Parent-By-EXEC}}}
                        } //end if
                    } //end if
                } catch (err) {
                    _p$.error(_N$, _m$, 'ERR: Failed with iFrame:', err);
                } //end try catch
                //--
            } else { // if a popup lost parent reference or a manual opened link, try to close it
                //--
                try {
                    self.close(); // it may fail if window reference with parent is lost (aka orphan window), needs try/catch
                } catch (err) {}
                //--
            } //end if else
            //--
        }; //END
        _C$.CloseModalPopUp = CloseModalPopUp; // export


        /**
         * Delayed Close a Modal / PopUp child browser window.
         * If a parent refresh is pending will execute it after.
         * @hint It is the prefered way to close a child window (modal iFrame / PopUp) using a button or just executing some js code in a child page.
         *
         * @memberof smartJ$Browser
         * @method CloseDelayedModalPopUp
         * @static
         *
         * @param 	{Integer} 	timeout 		The time delay in milliseconds ; If is NULL or 0 (zero) will use the default timeout
         *
         * @fires Closes a Modal Box or PopUp Window
         * @listens TimeOut counter on Set
         */
        const CloseDelayedModalPopUp = function(timeout) { // ES6
            //--
            timeout = _Utils$.format_number_int(timeout, false);
            if (timeout <= 0) {
                timeout = _Utils$.format_number_int(_C$.param_TimeDelayCloseWnd, false);
            } //end if
            //--
            if (timeout < 100) {
                timeout = 100; // min 0.1 sec.
            } else if (timeout > 60000) {
                timeout = 60000; // max 60 sec.
            } //end if
            //--
            setTimeout(() => {
                setFlag('PageAway', true);
                CloseModalPopUp();
            }, timeout);
            //--
        }; //END
        _C$.CloseDelayedModalPopUp = CloseDelayedModalPopUp; // export


        /**
         * Get the Pointer Event pageX / pageY or clientX / clientY coordinates, depending how viwePortOnly is set.
         * It works on both: mouse or touch devices.
         * On touch devices will look after: event.touches[0]
         * On mouse devices directly to: event
         * The pageX/Y coordinates are relative to the top left corner of the whole rendered page (including parts hidden by scrolling), while clientX/Y coordinates are relative to the top left corner of the visible part of the page (aka ViewPort)
         *
         * @param 	{Event} 	evt 					The EVENT Object Reference (must be a pointer event: mouse or touch)
         * @param 	{Boolean} 	viwePortOnly 			Default is FALSE ; if TRUE will return clientX / clientY instead of pageX / pageY
         *
         * @memberof smartJ$Browser
         * @method getCurrentPointerEventXY
         * @static
         *
         * @return 	{Object} 							An object with x and y coordinates as: { x: 123, y: 234, t: 'mousemove' } or { x: 123, y: 234, t: 'touchmove' } ; t (type) can be also other event type ... any of supported by browser ; if can't detect will return by default: { x: -1, y: -1 }
         */
        const getCurrentPointerEventXY = function(evt, viwePortOnly = false) {
            //--
            let coordinates = {
                x: -1,
                y: -1,
                t: null,
                q: null
            };
            //--
            if (!evt) {
                return coordinates;
            } //end if
            //--
            let pointEv = evt;
            if (evt.touches && evt.touches.length > 0) {
                pointEv = evt.touches[0];
            } //end if
            //--
            let X = -1;
            let Y = -1;
            if (viwePortOnly === true) {
                coordinates.q = 'clientXY';
                X = pointEv.clientX || -1;
                Y = pointEv.clientY || -1;
            } else {
                coordinates.q = 'pageXY';
                X = pointEv.pageX || -1;
                Y = pointEv.pageY || -1;
            } //end if else
            coordinates.x = _Utils$.format_number_int(X);
            coordinates.y = _Utils$.format_number_int(Y);
            coordinates.t = _Utils$.stringPureVal(evt.type || 'unknown', true).toLowerCase();
            //--
            return coordinates;
            //--
        }; //END
        _C$.getCurrentPointerEventXY = getCurrentPointerEventXY; // export


        /**
         * Get the highest available Z-Index from a browser page taking in account all visible layers (div).
         * It will ignore non-visible layers for speed-up, as they have anyway no Z-Index assigned.
         *
         * @memberof smartJ$Browser
         * @method getHighestZIndex
         * @static
         *
         * @return 	{Integer} 							The highest available Z-Index from current page
         */
        const getHighestZIndex = function() {
            //-- inits
            let index_highest = 1;
            let index_current = 1;
            //--
            $('div').each((i, el) => { // it will scan just divs to be efficient
                let $el = $(el);
                if (
                    ($el.css('display') == 'none') ||
                    ($el.css('visibility') == 'hidden') ||
                    ($el.attr('id') == 'SmartFramework___Debug_InfoBar') // it should be not considered
                ) {
                    // skip
                } else {
                    index_current = _Utils$.format_number_int(parseInt($el.css('z-index'), 10));
                    if (index_current > 0) {
                        if (index_current > index_highest) {
                            index_highest = index_current;
                        } //end if
                    } //end if
                } //end if else
            });
            //--
            if (index_highest < 2147483647) {
                index_highest += 1; // return next
            } //end if
            //--
            return index_highest;
            //--
        }; //END
        _C$.getHighestZIndex = getHighestZIndex; // export


        /**
         * Create a Max Container arround a HTML Element that will add the Maximize View handler over it ; element must be a layer (div, iframe, ...).
         * It will add a maximize / un-maximize button and the handler for it to be able to maximize the element to match full window width and height.
         * @hint When the selected element will be maximized the page will be protected by an overlay (all content below the element).
         *
         * @memberof smartJ$Browser
         * @method createMaxContainer
         * @static
         *
         * @param 	{String} 	id 			The HTML id of the HTML element to bind to
         * @param 	{Boolean} 	maximized 	*Optional* Default is FALSE ; If set to TRUE the element will be auto-maximized when bind to it
         */
        const createMaxContainer = function(id, maximized = false) {
            //--
            id = _Utils$.stringPureVal(id, true); // cast to string, trim
            id = _Utils$.create_htmid(id);
            if (id == '') {
                _p$.error(_N$, 'ERR: createMaxContainer: empty ID');
                return;
            } //end if
            //--
            const $el = $('#' + id);
            //--
            if (($el.attr('data-fullscreen') == 'default') || ($el.attr('data-fullscreen') == 'fullscreen')) {
                return; // avoid apply twice
            } //end if
            //--
            $el.attr('data-fullscreen', 'default').append('<div style="position:absolute; top:-4px; left:-4px; width:20px; height: 20px; overflow:hidden; text-align:center; cursor:pointer; opacity:0.7;" title="Toggle Element Full Screen"><img height="20" src="' + _Utils$.escape_html(_C$.param_IconImgFullScreen) + '"></div>').css({
                'position': 'relative',
                'border': '1px solid #DDDDDD',
                'z-index': 1
            }).click((btn) => {
                const $btn = $(btn.currentTarget);
                if ($btn.attr('data-fullscreen') == 'fullscreen') {
                    OverlayHide();
                    $btn.attr('data-fullscreen', 'default');
                    $btn.css({
                        'position': 'relative',
                        'top': 0,
                        'left': 0,
                        'width': Math.max(_Utils$.format_number_int(parseInt($btn.attr('data-width'))), 100),
                        'height': Math.max(_Utils$.format_number_int(parseInt($btn.attr('data-height'))), 100),
                        'z-index': 1
                    });
                } else {
                    OverlayShow();
                    OverlayClear();
                    $btn.attr('data-width', String(_Utils$.escape_html(String($btn.width()))));
                    $btn.attr('data-height', String(_Utils$.escape_html(String($btn.height()))));
                    $btn.attr('data-fullscreen', 'fullscreen');
                    $btn.css({
                        'position': 'fixed',
                        'top': '7px',
                        'left': '7px',
                        'width': '99%',
                        'height': '98%',
                        'z-index': 2147403000
                    });
                } //end if else
            });
            //--
            if (maximized === true) {
                $el.trigger('click');
            } //end if
            //--
        }; //END
        _C$.createMaxContainer = createMaxContainer; // export


        /**
         * Display a page overlay in the current browser window
         * All page elements that must be visible over the overlay must have zIndex in range: 2147401000 - 214740999
         * @hint The ZIndex of the overlay is: 2147400000
         *
         * @memberof smartJ$Browser
         * @method OverlayShow
         * @static
         *
         * @param 	{String} 	text 			*Optional* a text to display in overlay as notification
         * @param 	{String} 	title 			*Optional* a title to display for overlay as notification
         * @param 	{String} 	css_class 		*Optional* a CSS class name for the notification (if any)
         * @param 	{String} 	overlay_id 		*Optional* the overlay ID ; default is null and will use a hard-coded ID
         * @param 	{Boolean} 	clear 			*Optional* clear the overlay from loading img
         * @return 	{Object} 					The overlay as HTML object
         */
        const OverlayShow = function(text, title, css_class, overlay_id = null, clear = false) { // ES6
            //--
            text = _Utils$.stringPureVal(text, true); // cast to string, trim
            title = _Utils$.stringPureVal(title, true); // cast to string, trim
            //--
            css_class = _Utils$.stringPureVal(css_class, true); // cast to string, trim
            if (css_class == '') {
                css_class = 'white';
            } //end if
            //--
            overlay_id = OverlayValidID(overlay_id);
            //--
            let the_style = 'style="display: none; background-color:' + _Utils$.escape_html(_C$.param_CssOverlayBgColor) + '; opacity: ' + _Utils$.escape_html(_C$.param_CssOverlayOpacity) + '; position: fixed; top: -50px; left: -50px; width: 1px; height: 1px;"';
            if (typeof(smartJ$UI) != 'undefined') {
                if ((typeof(smartJ$UI.overlayCssClass) != 'undefined') && (smartJ$UI.overlayCssClass != null) && (smartJ$UI.overlayCssClass != '')) {
                    the_style = 'class="' + _Utils$.escape_html(smartJ$UI.overlayCssClass) + '"'; // integrate with UI's Overlay
                } //end if
            } //end if
            //--
            const have_growl = (GrowlSelectType() != '') ? true : false;
            //--
            let inner_html = '<img src="' + _Utils$.escape_html(_C$.param_LoaderImg) + '" alt="... loading ..." style="background:transparent!important;color:#555555!important;opacity:1!important;">';
            if (clear === true) {
                inner_html = '';
            } //end if
            if (have_growl !== true) {
                inner_html = inner_html + '<div>' + '<h1>' + title + '</h1>' + '<div>' + text + '</div>' + '</div>';
            } //end if else
            //--
            $('#' + overlay_id).remove(); // remove any instance if previous exist
            const overlay = $('<div id="' + _Utils$.escape_html(overlay_id) + '" ' + the_style + '></div>').css({
                'z-index': 2147400000,
                'position': 'fixed',
                'top': '0px',
                'left': '0px',
                'width': '100%',
                'height': '100%'
            }).hide().appendTo('body');
            $('#' + overlay_id).html('<div style="width:100%; position:fixed; top:25px; left:0px;"><center><div>' + inner_html + '</div></center></div>');
            //--
            try {
                overlay.fadeIn();
            } catch (err) {
                overlay.show();
            } //end try catch
            //--
            if ((text != '') || (title != '')) {
                if (have_growl === true) {
                    GrowlNotificationAdd(title, text, '', 500, false, css_class);
                } //end if else
            } //end if else
            //--
            return overlay;
            //--
        }; //END
        _C$.OverlayShow = OverlayShow; // export


        /**
         * Clear all notifications from the page overlay in the current browser window
         *
         * @memberof smartJ$Browser
         * @method OverlayClear
         * @static
         * @arrow
         *
         * @param 	{String} 	overlay_id 		*Optional* the overlay ID ; default is null and will use a hard-coded ID
         */
        const OverlayClear = (overlay_id = null) => { // ES6
            //--
            overlay_id = OverlayValidID(overlay_id);
            //--
            $('#' + overlay_id).empty().html('');
            //--
        }; //END
        _C$.OverlayClear = OverlayClear; // export


        /**
         * Hide (destroy) the page overlay in the current browser window
         *
         * @memberof smartJ$Browser
         * @method OverlayHide
         * @static
         */
        const OverlayHide = function(overlay_id = null) { // ES6
            //--
            overlay_id = OverlayValidID(overlay_id);
            //--
            let overlay = $('#' + overlay_id);
            //--
            try {
                overlay.fadeOut();
            } catch (err) {
                overlay.hide();
            } //end try catch
            //--
            overlay.css({
                'z-index': 1,
                'position': 'fixed',
                'top': '-50px',
                'left': '-50px',
                'width': 1,
                'height': 1
            }).remove(); // remove the instance
            //--
        }; //END
        _C$.OverlayHide = OverlayHide; // export


        /**
         * Create a Message Growl Notification
         *
         * @memberof smartJ$Browser
         * @method GrowlNotificationAdd
         * @static
         *
         * @param 	{String} 		title 				a title for the notification (HTML) ; it can be empty string
         * @param 	{String} 		text 				the main notification message (HTML) ; it is mandatory
         * @param 	{String} 		image 				the URL link to a notification icon image (svg/gif/png/jpg/webp) or null
         * @param 	{Integer} 		time 				the notification display time in milliseconds ; use 0 for sticky ; between 0 (0 sec) and 60000 (60 sec)
         * @param 	{Boolean} 		sticky 				*Optional* FALSE by default (will auto-close after the display time expire) ; TRUE to set sticky (require manual close, will not auto-close)
         * @param 	{Enum} 			css_class 			*Optional* a CSS class name for the notification or empty string to use default one: darknote (black), notice (white), info (blue), success (green), warning (yellow), error (red)
         * @param 	{Array-Obj} 	options 			*Optional* Extra Growl Properties:
         * 		{ // example of extra Options
         * 			before_open: 	() => {},
         * 			after_open: 	() => {},
         * 			before_close: 	() => {},
         * 			after_close: 	() => {}
         * 		}
         * @return 	{Boolean} 							TRUE if Success FALSE if Fail
         */
        const GrowlNotificationAdd = function(title, html, image, time, sticky = false, css_class = null, options = null) { // ES6 {{{SYNC-JS-GROWL-TRANSLATE-CLASSES}}}
            //--
            const _m$ = 'GrowlNotificationAdd';
            //--
            time = _Utils$.format_number_int(time, false);
            if (time < 0) {
                time = 0;
            } else if (time > 60000) {
                time = 60000;
            } //end if
            //--
            if ((image != undefined) && (image != '') && (image !== false)) { // undef tests also for null
                image = '<img src="' + _Utils$.escape_html(image) + '" align="right">';
            } else {
                image = '';
            } //end if
            //--
            css_class = _Utils$.stringPureVal(css_class, true); // cast to string, trim
            if (css_class == 'undefined') {
                css_class = '';
            } //end if
            //--
            let growl_before_open = null;
            let growl_after_open = null;
            let growl_before_close = null;
            let growl_after_close = null;
            //--
            if (options) {
                if (options.hasOwnProperty('before_open')) {
                    growl_before_open = options.before_open;
                } //end if
                if (options.hasOwnProperty('after_open')) {
                    growl_after_open = options.after_open;
                } //end if
                if (options.hasOwnProperty('before_close')) {
                    growl_before_close = options.before_close;
                } //end if
                if (options.hasOwnProperty('after_close')) {
                    growl_after_close = options.after_close;
                } //end if
            } //end if
            //--
            const theGrowlType = GrowlSelectType();
            //--
            if (theGrowlType === 'gritter') {
                //--
                css_class = String($.gritter.translateCssClasses(css_class));
                //--
                try {
                    $.gritter.add({
                        class_name: String(css_class),
                        title: String(String(title) + String(image)),
                        text: String(html),
                        before_open: growl_before_open,
                        after_open: growl_after_open,
                        before_close: growl_before_close,
                        after_close: growl_after_close,
                        time: (sticky ? 0 : time),
                        sticky: !!sticky, // bool
                    });
                } catch (err) {
                    _p$.error(_N$, _m$, 'ERR: gritter:', err);
                } // end try catch
                //--
            } else if (theGrowlType === 'toastr') {
                //--
                css_class = $.toastr.translateCssClasses(css_class);
                //--
                try {
                    $.toastr.notify({
                        appearanceClass: String(css_class),
                        title: String(String(title) + String(image)),
                        message: String(html),
                        onBeforeVisible: growl_before_open,
                        onVisible: growl_after_open,
                        onBeforeHidden: growl_before_close,
                        onHidden: growl_after_close,
                        timeOut: (sticky ? 0 : time),
                    });
                } catch (err) {
                    _p$.error(_N$, _m$, 'ERR: toastr:', err);
                } //end try catch
                //--
            } else { // fallback to dialog
                //--
                if ($.isFunction(growl_before_open)) {
                    growl_before_open();
                } //end if
                if ($.isFunction(growl_after_open)) {
                    growl_after_open();
                } //end if
                let fx = () => {
                    if ($.isFunction(growl_before_close)) {
                        growl_before_close();
                    } //end if
                    if ($.isFunction(growl_after_close)) {
                        growl_after_close();
                    } //end if
                };
                AlertDialog(html, fx, title);
                //--
                return false;
                //--
            } //end if else
            //--
            return true;
            //--
        }; //END
        _C$.GrowlNotificationAdd = GrowlNotificationAdd; // export


        /**
         * Remove all Growl Notifications from the current browser window (page)
         *
         * @memberof smartJ$Browser
         * @method GrowlNotificationRemove
         * @static
         *
         * @return 	{Boolean} 					TRUE if Success FALSE if Fail
         */
        const GrowlNotificationRemove = function() {
            //--
            const growlType = GrowlSelectType();
            //--
            switch (growlType) {
                case 'gritter':
                    $.gritter.removeAll();
                    return true;
                    break;
                case 'toastr':
                    $.toastr.clear();
                    return true;
                    break;
            } //end switch
            //--
            return false;
            //--
        }; //END
        _C$.GrowlNotificationRemove = GrowlNotificationRemove; // export


        /**
         * Create a Browser Alert Dialog that will have just the OK button.
         * It will detect if the UIDialog is available and will prefer to use it if set.
         * Otherwise will try to detect if SimpleDialog is available and will use it if UIDialog is not available - fallback.
         * If none of the above are available will try to use jQuery.alertable/alert else just display a simple alert() - fallback.
         *
         * @memberof smartJ$Browser
         * @method AlertDialog
         * @static
         *
         * @param 	{String} 	y_message 		The message to display (HTML)
         * @param 	{JS-Code} 	evcode 			*Optional* the JS Code to execute on closing the alert / Dialog by pressing the OK button (the alert / Dialog can be closed only if OK button is clicked)
         * @param 	{String} 	y_title 		*Optional* a title for the alert / Dialog (HTML)
         * @param 	{Integer} 	y_width 		*Optional* the width of the Dialog (will be used just if UIDialog / SimpleDialog are detected) ; if not set the default width will be used
         * @param 	{Integer} 	y_height 		*Optional* the height of the Dialog (will be used just if UIDialog / SimpleDialog are detected) ; if not set the default height will be used
         * @param 	{String} 	dialogType 		*Optional* The Dialog Type ; default is 'auto'
         *
         * @fires open an Alert Dialog (custom or default, depends on settings)
         */
        const AlertDialog = function(y_message, evcode = null, y_title = '', y_width = null, y_height = null, dialogType = 'auto') {
            //--
            const _m$ = 'AlertDialog';
            //--
            y_message = _Utils$.stringPureVal(y_message, true); // cast to string, trim
            y_title = _Utils$.stringPureVal(y_title, true); // cast to string, trim
            //--
            if (dialogType === 'auto') {
                dialogType = DialogSelectType();
            } //end if
            //--
            if (dialogType === 'ui') { // use UI Dialog (the best choice)
                //--
                smartJ$UI.DialogAlert(y_message, evcode, y_title, y_width, y_height);
                //--
            } else if (dialogType === 'dialog') { // use simple dialog
                //--
                SmartSimpleDialog.Dialog_Alert(y_message, evcode, y_title, y_width, y_height);
                //--
            } else { // fallback to alertable or native browser alert
                //--
                const evErrMsg = 'ERR: JS-Eval Failed on DialogAlert';
                //--
                if (dialogType === 'alertable') {
                    //--
                    $.alertable.alert((y_title ? '<div style="' + _Utils$.escape_html(cssAlertable) + '">' + y_title + '</div>' : '') + y_message, {
                        html: true,
                        width: ((y_width && (y_width >= 275) && (y_width <= 960)) ? y_width : 550),
                        height: ((y_height && (y_height >= 100) && (y_height <= 720)) ? y_height : 270)
                    }).always(() => { // use always not done to simulate real alert
                        _Utils$.evalJsFxCode(_N$ + '.' + _m$ + ' (1)', evcode); // EV.NOCTX
                    });
                    //--
                } else { // native
                    //--
                    y_title = stripTags(y_title);
                    y_message = stripTags(y_message);
                    //--
                    alert(y_title + '\n' + y_message);
                    _Utils$.evalJsFxCode(_N$ + '.' + _m$ + ' (2)', evcode); // EV.NOCTX
                    //--
                } //end if else
                //--
            } //end if else
            //--
        }; //END
        _C$.AlertDialog = AlertDialog;


        /**
         * Create a Browser Confirm Dialog that will have 2 buttons: OK / Cancel.
         * It will detect if the UIDialog is available and will prefer to use it if set.
         * Otherwise will try to detect if SimpleDialog is available and will use it if UIDialog is not available - fallback.
         * If none of the above are available will try to use jQuery.alertable/confirm else display a simple confirm() - fallback.
         *
         * @memberof smartJ$Browser
         * @method ConfirmDialog
         * @static
         *
         * @param 	{String} 	y_question 		The question / message to display (HTML)
         * @param 	{JS-Code} 	evcode 			*Optional* the JS Code to execute on closing the confirm / Dialog by pressing the OK button (the confirm / Dialog can be closed by either OK button clicked or Cancel button clicked ; the code will be not executed on Cancel button click / close)
         * @param 	{String} 	y_title 		*Optional* a title for the confirm / Dialog (HTML)
         * @param 	{Integer} 	y_width 		*Optional* the width of the Dialog (will be used just if UIDialog / SimpleDialog are detected) ; if not set the default width will be used
         * @param 	{Integer} 	y_height 		*Optional* the height of the Dialog (will be used just if UIDialog / SimpleDialog are detected) ; if not set the default height will be used
         * @param 	{String} 	dialogType 		*Optional* The Dialog Type ; default is 'auto'
         *
         * @fires open a Confirmation Dialog (custom or default, depends on settings)
         */
        const ConfirmDialog = function(y_question, evcode = null, y_title = '', y_width = null, y_height = null, dialogType = 'auto') { // ES6
            //--
            const _m$ = 'ConfirmDialog';
            //--
            y_question = _Utils$.stringPureVal(y_question, true); // cast to string, trim
            y_title = _Utils$.stringPureVal(y_title, true); // cast to string, trim
            //--
            if (dialogType === 'auto') {
                dialogType = DialogSelectType();
            } //end if
            //--
            if (dialogType === 'ui') { // use UI Dialog (the best choice)
                //--
                smartJ$UI.DialogConfirm(y_question, evcode, y_title, y_width, y_height);
                //--
            } else if (dialogType === 'dialog') { // use simple dialog
                //--
                SmartSimpleDialog.Dialog_Confirm(y_question, evcode, y_title, y_width, y_height);
                //--
            } else { // fallback to alertable or native browser confirm dialog
                //--
                const evErrMsg = 'ERR: JS-Eval Failed on DialogConfirm';
                //--
                if (dialogType === 'alertable') {
                    //--
                    $.alertable.confirm((y_title ? '<div style="' + _Utils$.escape_html(cssAlertable) + '">' + y_title + '</div>' : '') + y_question, {
                        html: true,
                        width: ((y_width && (y_width >= 275) && (y_width <= 960)) ? y_width : 550),
                        height: ((y_height && (y_height >= 100) && (y_height <= 720)) ? y_height : 270)
                    }).then(() => {
                        _Utils$.evalJsFxCode(_N$ + '.' + _m$ + ' (1)', evcode); // EV.NOCTX
                    });
                    //--
                } else { // native
                    //--
                    y_title = stripTags(y_title);
                    y_question = stripTags(y_question);
                    //--
                    let the_confirmation = confirm(y_title + '\n' + y_question);
                    if (the_confirmation) {
                        _Utils$.evalJsFxCode(_N$ + '.' + _m$ + ' (2)', evcode); // EV.NOCTX
                    } //end if
                    //--
                } //end if else
                //--
            } //end if else
            //--
        }; //END
        _C$.ConfirmDialog = ConfirmDialog; // export


        /**
         * Create a Message (Dialog or Growl) Notification
         *
         * @memberof smartJ$Browser
         * @method MessageNotification
         * @static
         * @param 	{String} 	ytitle 						The Title
         * @param 	{String} 	ymessage 					The Message (HTML code)
         * @param 	{String} 	yredirect 					*Optional* The URL to redirect
         * @param 	{Yes/No} 	growl 						*Optional* If 'yes' will use the Growl notifications otherwise (default: if 'no') will use Dialog notification
         * @param 	{Enum} 		class_growl 				*Optional* If Growl is used, a CSS class for Growl is required
         * @param 	{Integer} 	timeout 					*Optional* If Growl is used, the Growl timeout in milliseconds
         */
        const MessageNotification = function(ytitle, ymessage, yredirect, growl, class_growl, timeout) { // ES6
            //--
            yredirect = _Utils$.stringPureVal(yredirect, true); // cast to string, trim
            growl = _Utils$.stringPureVal(growl, true); // cast to string, trim
            timeout = _Utils$.format_number_int(timeout, false);
            //--
            if (growl === 'yes') {
                //--
                let redirectAfterClose = null;
                if ((yredirect != undefined) && (yredirect != '')) { // undef tests also for null
                    RedirectDelayedToURL(yredirect, timeout + 750); // let this just in case if growl fails to close because of user interraction ; a redirect is mandatory !
                    redirectAfterClose = () => {
                        RedirectDelayedToURL(String(yredirect), 250); // must be 250 here to close overlay before redirects
                    };
                } //end if
                //--
                const growlOptions = {
                    before_close: redirectAfterClose
                };
                //--
                GrowlNotificationAdd(ytitle, ymessage, '', timeout, false, class_growl, growlOptions);
                //--
            } else {
                //--
                let fx = null;
                if ((yredirect != undefined) && (yredirect != '')) { // undef tests also for null
                    fx = () => {
                        RedirectDelayedToURL(yredirect, 100);
                    };
                } //end if
                //--
                AlertDialog(ymessage, fx, ytitle, 550, 275);
                //--
            } //end if else
            //--
        }; //END
        _C$.MessageNotification = MessageNotification; // export


        /**
         * Confirm a Form Submit by a confirm / Dialog, with OK and Cancel buttons.
         * The form will be submitted just if OK button is clicked.
         * @hint The function is using ConfirmDialog() and will detect and prefer in the specific order if UIDialog / SimpleDialog or just confirm() are available in Browser.
         *
         * @memberof smartJ$Browser
         * @method confirmSubmitForm
         * @static
         *
         * @param 	{String} 		y_confirm 		The question / message to display for confirmation (HTML)
         * @param 	{Html-Form} 	y_form 			The HTML Form Object to bind to
         * @param 	{String} 		y_target 		The window target to send the form to
         * @param 	{Integer} 		windowWidth 	*Optional* the width of the new Modal/PopUp child window if new window target is used ; if not set the default width will be used
         * @param 	{Integer} 		windowHeight 	*Optional* the height of the new Modal/PopUp child window if new window target is used ; if not set the default height will be used
         * @param 	{Enum} 			forcePopUp 		*Optional* if a new target window is required, 0 = default, use modal/iFrame if set by smartJ$Browser.param_ModalBoxActive, 1 = force PopUp ; -1 force modal/iFrame
         * @param	{Enum} 			forceDims 		*Optional* if set to 1 will try force set the width/height for the new modal/iFrame or PopUp
         *
         * @fires open a confirmation dialog
         * @listens form submit event
         */
        const confirmSubmitForm = function(y_confirm, y_form, y_target, windowWidth = 0, windowHeight = 0, forcePopUp = 0, forceDims = 0) { // ES6
            //--
            y_confirm = _Utils$.stringPureVal(y_confirm, true); // cast to string, trim
            //--
            if ((y_form == undefined) || (y_form == '')) {
                //--
                _p$.error(_N$, 'confirmSubmitForm', 'ERR: Form Object is undefined');
                return;
                //--
            } //end if
            //--
            let submit_fx = () => {
                y_form.submit();
            }; // by default we do just submit
            //--
            y_target = _Utils$.stringPureVal(y_target, true); // cast to string, trim
            if (y_target != '') {
                //--
                submit_fx = () => {
                    PopUpSendForm(
                        y_form, // object
                        String(y_target), // string
                        _Utils$.format_number_int(windowWidth, false), // int
                        _Utils$.format_number_int(windowHeight, false), // int
                        _Utils$.format_number_int(forcePopUp, false), // int
                        _Utils$.format_number_int(forceDims, false) // int
                    );
                };
                //--
            } //end if
            //--
            ConfirmDialog(y_confirm, submit_fx);
            //--
        }; //END
        _C$.confirmSubmitForm = confirmSubmitForm; // export


        /**
         * Open a Modal/iFrame or PopUp child window with a new target to post a form within.
         * It will get the form URL and form method GET/POST directly from the objForm.
         * The function must be called by a form button onClick followed by 'return false;' not by classic submit to avoid fire the form send twice 1st before (in a _blank window) and 2nd after opening the child popup/modal.
         * @hint The function if used in a button with 'return false;' will catch the form send behaviour and will trigger it just after the child modal/iFrame or PopUp child window (new) target is opened and available.
         *
         * @memberof smartJ$Browser
         * @method PopUpSendForm
         * @static
         *
         * @param 	{Html-Form} 	objForm 		The HTML Form Object reference
         * @param 	{String} 		strTarget 		The child window target to post the form to
         * @param 	{Integer} 		windowWidth 	*Optional* the width of the new Modal/PopUp child window if new window target is used ; if not set the default width will be used
         * @param 	{Integer} 		windowHeight 	*Optional* the height of the new Modal/PopUp child window if new window target is used ; if not set the default height will be used
         * @param 	{Enum} 			forcePopUp 		*Optional* if a new target window is required, 0 = default, use modal/iFrame if set by smartJ$Browser.param_ModalBoxActive, 1 = force PopUp ; -1 force modal/iFrame
         * @param	{Enum} 			forceDims 		*Optional* if set to 1 will try force uwing the width/height set for the new modal/iFrame or PopUp
         * @param 	{JS-Code} 		evcode 			*Optional* the JS Code to execute after submit the form
         *
         * @fires send a form in a pop-up window to avoid loose the current page
         * @listens form submit event
         */
        const PopUpSendForm = function(objForm, strTarget, windowWidth = 0, windowHeight = 0, forcePopUp = 0, forceDims = 0, evcode = null) { // ES6
            //--
            const _m$ = 'PopUpSendForm';
            //--
            strTarget = _Utils$.stringPureVal(strTarget, true); // cast to string, trim
            //-- try to get the form action
            let strUrl = '';
            try {
                strUrl = String(objForm.action || ''); // ensure string and get form action
            } catch (err) {
                _p$.error(_N$, _m$, 'ERR: Invalid Form Object');
                return;
            } //end try catch
            //-- if cross domain calls switching protocols http:// and https:// will be made will try to force pop-up to avoid XSS Error
            let crr_protocol = String(document.location.protocol);
            let crr_arr_url = strUrl.split(':');
            let crr_url = String(crr_arr_url[0]) + ':';
            //--
            if (
                ((crr_protocol === 'http:') || (crr_protocol === 'https:')) &&
                ((crr_url === 'http:') || (crr_url === 'https:')) &&
                (crr_url !== crr_protocol)
            ) {
                forcePopUp = 1;
            } //end if
            //--
            objForm.target = strTarget; // normal popUp use
            if ((((allowModalCascading()) && (forcePopUp != 1)) || (forcePopUp == -1)) && (allowModalBox())) {
                if (typeof(smartJ$ModalBox) != 'undefined') {
                    objForm.target = smartJ$ModalBox.getName(); // use smart modal box
                } //end if else
            } //end if else
            //--
            initModalOrPopUp(_C$.param_LoaderBlank, objForm.target, windowWidth, windowHeight, forcePopUp, forceDims);
            //--
            let fx = () => {
                objForm.submit();
            }; // by default we do just submit
            //--
            if ((evcode != undefined) && (evcode != '') && (evcode != 'undefined')) { // test undefined is also for null
                fx = () => {
                    objForm.submit();
                    try {
                        if (typeof(evcode) === 'function') {
                            evcode(objForm, strTarget, forcePopUp, forceDims);
                        } else {
                            eval(evcode);
                        } //end if else
                    } catch (err) {
                        _p$.error(_N$, _m$, 'ERR: Form After-Submit JS Code Failed:', err);
                    } //end try catch
                };
            } //end if
            //--
            setTimeout(fx, _C$.param_TimeDelayCloseWnd + 500); // delay submit, required
            //--
            return false;
            //--
        }; //END
        _C$.PopUpSendForm = PopUpSendForm; // export


        /**
         * Open a Modal/iFrame or PopUp child window with a new target to open a URL link within.
         * @hint The function can be called by a button, a link or other HTML elements at onClick (if 'a' element onClick is used must be followed by 'return false;' to avoid fire page refresh.
         *
         * @memberof smartJ$Browser
         * @method PopUpLink
         * @static
         *
         * @param 	{String} 	strUrl 			The URL link to be opened in a new browser child window target or modal iframe (modalbox)
         * @param 	{String} 	strTarget 		The child window target to post the form to ; name ; do not use _self or _blank here, and try ensure a compliant name
         * @param 	{Integer} 	windowWidth 	*Optional* the width of the new Modal/PopUp child window if new window target is used ; if not set the default width will be used
         * @param 	{Integer} 	windowHeight 	*Optional* the height of the new Modal/PopUp child window if new window target is used ; if not set the default height will be used
         * @param 	{Enum} 		forcePopUp 		*Optional* if a new target window is required, 0 = default, use modal/iFrame if set by smartJ$Browser.param_ModalBoxActive, 1 = force PopUp ; -1 force modal/iFrame
         * @param	{Enum} 		forceDims 		*Optional* if set to 1 will try force uwing the width/height set for the new modal/iFrame or PopUp
         * @param 	{Enum}		handlerMode		*Optional* If explicit set to 1 or 2 will use only a simple modal/popup without binding to the parent window ; if set to 2, the popup will not use redirect handler in addition to no binding ; if set to -1 the popup will not use redirect handler, but will use binding ; if set to -2 (default) will use same domain detection
         *
         * @fires open a Modal Box or a PopUp Window
         */
        const PopUpLink = function(strUrl, strTarget, windowWidth = 0, windowHeight = 0, forcePopUp = 0, forceDims = 0, handlerMode = -2) { // ES6
            //--
            strUrl = _Utils$.stringPureVal(strUrl, true); // cast to string, trim
            if (strUrl == '') {
                return;
            } //end if
            strUrl = String(strUrl || ''); // ensure string
            //--
            strTarget = _Utils$.stringPureVal(strTarget, true); // cast to string, trim
            //-- if cross domain calls between http:// and https:// will be made will try to force pop-up to avoid XSS Error
            const crr_protocol = String(document.location.protocol);
            const crr_arr_url = strUrl.split(':');
            const crr_url = crr_arr_url[0] + ':';
            //--
            if (
                ((crr_protocol === 'http:') || (crr_protocol === 'https:')) &&
                ((crr_url === 'http:') || (crr_url === 'https:')) &&
                (crr_url !== crr_protocol)
            ) {
                forcePopUp = 1;
            } //end if
            //--
            let crrHostName = null;
            let tgtHostName = null;
            try {
                crrHostName = new URL(document.location).hostname;
                tgtHostName = new URL(strUrl).hostname;
            } catch (err) {
                crrHostName = '';
                tgtHostName = '';
            }
            if ((crrHostName != '') && (tgtHostName != '') && (crrHostName != tgtHostName)) { // if on different host
                if (handlerMode < -1) {
                    handlerMode = 2; // on different domain don't use binding or redirect to avoid detect as a tricky script by browsers or SEO
                } //end if
            } //end if
            //	console.log('crrHostName', crrHostName);
            //	console.log('tgtHostName', tgtHostName);
            initModalOrPopUp(strUrl, strTarget, windowWidth, windowHeight, forcePopUp, forceDims, handlerMode);
            //--
            return false;
            //--
        }; //END
        _C$.PopUpLink = PopUpLink; // export


        /**
         * Get a Cookie from Browser by Name and return it's Value
         *
         * @memberof smartJ$Browser
         * @method getCookie
         * @static
         *
         * @param 	{String} 	name 			The cookie Name ; must be a valid cookie name
         * @return 	{String} 					The cookie Value
         */
        const getCookie = function(name) { // ES6
            //--
            name = _Utils$.stringPureVal(name, true); // cast to string, trim
            if ((name == '') || !(regexValidCookieName.test(name))) { // {{{SYNC-REGEX-URL-VARNAME}}}
                return null;
            } //end if
            //--
            let c;
            try {
                c = document.cookie.match(new RegExp('(^|;)\\s*' + _Utils$.preg_quote(String(name)) + '=([^;\\s]*)')); // Array
            } catch (err) {
                c = [];
                _p$.error(_N$, 'ERR: getCookie(' + name + ') Failed:', err);
            } //end try catch
            //--
            if (c && c.length >= 3) {
                const d = decodeURIComponent(c[2] || '') || ''; // fix to avoid working with null !!
                return String(d);
            } //end if
            //--
            return ''; // fix to avoid working with null !!
            //--
        }; //END
        _C$.getCookie = getCookie; // export


        /**
         * Set a Cookie in Browser by Name and Value
         *
         * @memberof smartJ$Browser
         * @method setCookie
         * @static
         *
         * @param 	{String} 	name 			The cookie Name
         * @param 	{String} 	value 			The cookie Value
         * @param	{Numeric} 	expire 			*Optional* The cookie expire time in seconds since now ; if set to zero will expire by session ; if set to -1 will be unset (deleted) ; if set to NULL will use the value from smartJ$Browser.param_CookieLifeTime ; default is 0
         * @param 	{String} 	path 			*Optional* The cookie path (default is /)
         * @param 	{String} 	domain 			*Optional* The cookie domain (default is @ and will use '@' to use the smartJ$Browser.param_CookieDomain)
         * @param 	{Enum} 		samesite 		*Optional* The SameSite cookie policy ; Can be: None, Lax, Strict ; (default is @ and will use '@' to use the smartJ$Browser.param_CookieSameSitePolicy)
         * @param 	{Boolean} 	secure 			*Optional* Force Cookie Secure Mode (default is FALSE)
         * @return 	{Boolean} 					TRUE on success and FALSE on failure
         */
        const setCookie = function(name, value, expire = 0, path = '/', domain = '@', samesite = '@', secure = false) { // ES6
            //--
            name = _Utils$.stringPureVal(name, true); // cast to string, trim
            if ((name == '') || !(regexValidCookieName.test(name))) { // {{{SYNC-REGEX-URL-VARNAME}}}
                _p$.warn(_N$, 'WARN: setCookie: Invalid Cookie Name:', name);
                return false;
            } //end if
            //--
            value = _Utils$.stringPureVal(value, true); // cast to string, trim
            //--
            let d = new Date();
            //--
            if (expire === null) {
                expire = _C$.param_CookieLifeTime;
            } //end if
            expire = _Utils$.format_number_int(expire); // allow negatives
            if (expire) {
                if (expire > 0) { // set with an expire date in the future
                    d.setTime(d.getTime() + (expire * 1000)); // now + (seconds)
                } else if (expire < 0) { // unset (set expire date in the past)
                    d.setTime(d.getTime() - (3600 * 24) - expire); // now - (1 day) - (seconds)
                    value = null; // unsetting a cookie needs an empty value
                } else { // expire by session
                    expire = 0; // explicit set to zero
                } //end if else
            } else {
                expire = 0;
            } //end if
            if (!value) {
                value = null;
                expire = -1; // force compatibility with PHP and unset empty cookies
            } //end if
            //--
            path = _Utils$.stringPureVal(path, true); // cast to string, trim
            path = String(path || '/');
            //--
            domain = _Utils$.stringPureVal(domain, true); // cast to string, trim
            if (domain === '@') { // NULL is not in the case
                domain = String(_C$.param_CookieDomain);
            } //end if
            //--
            samesite = _Utils$.stringPureVal(samesite, true); // cast to string, trim
            if (samesite === '@') { // NULL is not in the case
                samesite = String(_C$.param_CookieSameSitePolicy);
            } //end if
            samesite = String(samesite).toLowerCase();
            switch (samesite) {
                case 'none':
                    samesite = 'None';
                    secure = true; // new browsers require it if SameSite cookie policy is set explicit to None !!
                    break;
                case 'lax':
                    samesite = 'Lax';
                    break;
                case 'strict':
                    samesite = 'Strict';
                    break;
                default:
                    samesite = '';
            } //end switch
            //--
            // IMPORTANT: a cookie with the HttpOnly attribute set cannot be set or accessed by javascript, so ignore it
            //--
            try {
                document.cookie = _Utils$.escape_url(name) + '=' + _Utils$.escape_url(value) + (expire ? ('; expires=' + d.toGMTString()) : '') + '; path=' + path + (domain ? ('; domain=' + domain) : '') + (samesite ? '; SameSite=' + samesite : '') + (secure ? '; secure' : '');
                return true;
            } catch (err) {
                _p$.warn(_N$, 'WARN: Failed to setCookie:', err);
                return false;
            } //end try catch
            //--
        }; //END
        _C$.setCookie = setCookie; // export


        /**
         * Delete a Cookie from Browser by Name
         *
         * @memberof smartJ$Browser
         * @method deleteCookie
         * @static
         * @arrow
         *
         * @param 	{String} 	name 			The cookie Name
         * @param 	{String} 	path 			*Optional* The cookie path (default is /)
         * @param 	{String} 	domain 			*Optional* The cookie domain (default is @ and will use '@' to use the smartJ$Browser.param_CookieDomain)
         * @param 	{Enum} 		samesite 		*Optional* The SameSite cookie policy ; Can be: None, Lax, Strict ; (default is @ and will use '@' to use the smartJ$Browser.param_CookieSameSitePolicy)
         * @param 	{Boolean} 	secure 			*Optional* Force Cookie Secure Mode (default is FALSE)
         * @return 	{Boolean} 					TRUE on success and FALSE on failure
         */
        const deleteCookie = (name, path = '/', domain = '@', samesite = '@', secure = false) => setCookie(name, null, -1, path, domain, samesite, secure); // ES6
        _C$.deleteCookie = deleteCookie; // export


        /**
         * Force a text limit on a TextArea (will also attach a CounterField)
         *
         * @memberof smartJ$Browser
         * @method TextAreaAddLimit
         * @static
         *
         * @param 	{String} 	elemID 			The TextArea field name
         * @param 	{Integer+} 	maxChars 		The max limit of characters to accept in the TextArea
         *
         * @fires limits the text area for input more than max allowed characters
         * @listens typing text in a text area, paste or other input methods
         */
        const TextAreaAddLimit = function(elemID, maxChars) { // ES6
            //--
            elemID = _Utils$.stringPureVal(elemID); // cast to string, don't trim ! need to preserve the value
            if (elemID == '') {
                _p$.error(_N$, 'TextAreaAddLimit', 'Empty Element ID');
                return;
            } //end if
            //--
            maxChars = _Utils$.format_number_int(maxChars, false);
            if (maxChars < 1) {
                _p$.warn(_N$, 'ERR: TextArea Add Limit: Invalid Limit, will reset to 1');
                maxChars = 1;
            } //end if
            //--
            $('#' + elemID).on('change click blur keydown keyup paste', (evt) => {
                //--
                const field = $(evt.currentTarget);
                if (field.val().length > maxChars) { // if too long then trim it!
                    field.val(field.val().substring(0, maxChars));
                } //end if
                //--
                field.attr('title', '# Max: ' + maxChars + ' ; Chars: ' + (maxChars - field.val().length) + ' #'); // update the counter
                //--
            }).attr('title', '# Max: ' + maxChars + ' #').attr('maxlength', maxChars);
            //--
        }; //END
        _C$.TextAreaAddLimit = TextAreaAddLimit; // export


        /**
         * Catch ENTER Key in an Input[type=text] or other compatible input field
         * @hint Usage: onKeyDown="smartJ$Browser.catchKeyENTER(event);"
         *
         * @memberof smartJ$Browser
         * @method catchKeyENTER
         * @static
         *
         * @param 	{Event} 	evt 			The EVENT Object Reference
         *
         * @fires Catch and Disable use of ENTER key in a field
         * @listens ENTER Key press event
         *
         * @example				<input type="text" onKeyDown="smartJ$Browser.catchKeyENTER(event);">
         */
        const catchKeyENTER = function(evt) { // ES6
            //--
            if (!evt) {
                return true;
            } //end if
            //--
            let key = null;
            //--
            if (evt.key) {
                key = evt.key; // string
            } else if (evt.which) { // jQuery standard, deprecated
                key = evt.which; // numeric
            } else if (evt.keyCode) { // deprecated
                key = evt.keyCode; // numeric
            } //end if else # evt.code does not have native support in jQuery and the key codes are strings
            //--
            if ((key === 'Enter') || (key == 13)) {
                evt.preventDefault();
                return false;
            } //end if
            //--
            return true;
            //--
        }; //END
        _C$.catchKeyENTER = catchKeyENTER;


        /**
         * Catch TAB Key and insert a TAB character (\t) at the cursor instead to switch to next input field as default in html
         * It works on TextArea or other compatible input fields ...
         * @hint Usage: onKeyDown="smartJ$Browser.catchKeyTAB(event);"
         *
         * @memberof smartJ$Browser
         * @method catchKeyTAB
         * @static
         *
         * @param 	{Event} 	evt 			The EVENT Object Reference
         *
         * @fires Insert a real TAB character in the selected input
         * @listens TAB Key press event
         *
         * @example				<textarea id="txt" onKeyDown="smartJ$Browser.catchKeyTAB(event);">
         */
        const catchKeyTAB = function(evt) { // ES6
            //--
            if (!evt) {
                return;
            } //end if
            //--
            let key = null;
            if (evt.key) {
                key = evt.key; // string
            } else if (evt.which) { // jQuery standard, deprecated
                key = evt.which; // numeric
            } else if (evt.keyCode) { // deprecated
                key = evt.keyCode; // numeric
            } //end if else # evt.code does not have native support in jQuery and the key codes are strings
            //--
            if ((key === 'Tab') || (key == 9)) {
                //-- Tab key - insert tab expansion
                evt.preventDefault();
                //-- defs
                const tab = "\t";
                const t = evt.target;
                const ss = t.selectionStart;
                const se = t.selectionEnd;
                const scrollTop = t.scrollTop;
                const scrollLeft = t.scrollLeft;
                //-- Special case of multi line selection
                if (ss != se && t.value.slice(ss, se).indexOf("\n") != -1) {
                    //-- In case selection was not of entire lines (e.g. selection begins in the middle of a line) we have to tab at the beginning as well as at the start of every following line.
                    const pre = t.value.slice(0, ss);
                    const sel = t.value.slice(ss, se).replace(/\n/g, "\n" + tab);
                    const post = t.value.slice(se, t.value.length);
                    //--
                    t.value = pre.concat(tab).concat(sel).concat(post);
                    t.selectionStart = ss + tab.length;
                    t.selectionEnd = se + tab.length;
                } else {
                    //-- The Normal Case (no selection or selection on one line only)
                    t.value = t.value.slice(0, ss).concat(tab).concat(t.value.slice(ss, t.value.length));
                    if (ss == se) {
                        t.selectionStart = t.selectionEnd = ss + tab.length;
                    } else {
                        t.selectionStart = ss + tab.length;
                        t.selectionEnd = se + tab.length;
                    } //end if
                } //end if else
                //--
                t.scrollTop = scrollTop;
                t.scrollLeft = scrollLeft;
                //--
            } //end if
            //--
        }; //END
        _C$.catchKeyTAB = catchKeyTAB; // export


        /**
         * Check or Uncheck all checkboxes in a form element
         *
         * @memberof smartJ$Browser
         * @method CheckAllCheckBoxes
         * @static
         *
         * @param 	{String} 	y_form_name 			The form name (if empty string will operate on all checkboxes in the page otherwise just inside the form)
         * @param 	{String} 	y_element_id 			The checkboxes element ID
         * @param 	{Boolean} 	y_element_checked 		If TRUE will do check ; If FALSE will do uncheck ; otherwise will just inverse check
         *
         * @fires check/uncheck all the checkboxes inside a form
         */
        const CheckAllCheckBoxes = function(y_form_name, y_element_id, y_element_checked) { // ES6
            //--
            let selector = 'body';
            if (y_form_name) {
                selector = 'form[name="' + _Utils$.escape_html(y_form_name) + '"]';
            } //end if
            selector += ' input[type="checkbox"]';
            //--
            let checked = !y_element_checked;
            $(selector).each((i, el) => {
                const $el = $(el);
                checked ? $el.prop('checked', false) : $el.prop('checked', true); // $el.is(':checked');
            });
            //--
            return false;
            //--
        }; //END
        _C$.CheckAllCheckBoxes = CheckAllCheckBoxes; // export


        /**
         * Clone a HTML Element
         *
         * @example
         * <div id="multifile_list" style="text-align:left; max-width:550px;">
         * 		<input id="multifile_uploader" type="file" name="myvar[]" style="width:90%;">
         * </div>
         * <script>
         * 		smartJ$Browser.CloneElement('multifile_uploader', 'multifile_list', 'file-input', 10);
         * </script>
         *
         * @memberof smartJ$Browser
         * @method CloneElement
         * @static
         *
         * @param 	{String} 	elID 					The element ID to be cloned
         * @param 	{String} 	containerID 			The destination container ID
         * @param 	{Enum} 		elType 					The type of the element to be cloned: text-input ; text-area ; file-input ; html-element
         * @param 	{Integer} 	maxLimit 				The max limit number of elements includding the element itself and the cloned elements: between 1 and 255
         */
        const CloneElement = function(elID, containerID, elType, maxLimit) { // ES6
            //--
            const _m$ = 'CloneElement';
            //--
            elID = _Utils$.stringPureVal(elID, true); // cast to string, trim
            elID = _Utils$.create_htmid(elID);
            if (elID == '') { // undef tests also for null
                _p$.error(_N$, _m$, 'ERR: Invalid elID');
                return;
            } //end if
            //--
            containerID = _Utils$.stringPureVal(containerID, true); // cast to string, trim
            containerID = _Utils$.create_htmid(containerID);
            if (containerID == '') { // undef tests also for null
                _p$.error(_N$, _m$, 'ERR: Invalid containerID');
                return;
            } //end if
            //--
            maxLimit = _Utils$.format_number_int(maxLimit, false);
            if (maxLimit < 1) {
                maxLimit = 1; // min limit
            } else if (maxLimit > 255) {
                maxLimit = 255; // hardcoded max limit
            } //end if
            //-- init
            const $el = $('#' + elID);
            const control_num = _Utils$.format_number_int(parseInt($('body').find('[id^=' + 'clone_control__' + elID + ']').length), false);
            if (control_num <= 0) {
                $el.before('<img id="' + 'clone_control__' + _Utils$.escape_html(elID) + '" alt="AddNew" title="Add New (Max: ' + _Utils$.escape_html(maxLimit) + ')" height="16" src="' + _Utils$.escape_html(_C$.param_ImgCloneInsert) + '" style="cursor:pointer; vertical-align:middle;" onClick="smartJ$Browser.CloneElement(\'' + _Utils$.escape_js(elID) + '\', \'' + _Utils$.escape_js(containerID) + '\', \'' + _Utils$.escape_js(elType) + '\', ' + _Utils$.format_number_int(maxLimit) + ');' + '">&nbsp;&nbsp;</span>');
                return;
            } //end if
            //-- do clone
            let cloned_num = _Utils$.format_number_int(_Utils$.format_number_int($('body').find('[id^=' + 'clone_of__' + elID + '_' + ']').length), false);
            if (cloned_num <= 0) {
                cloned_num = 0;
            } //end if
            if (cloned_num >= (maxLimit - 1)) {
                return;
            } //end if
            //--
            const date = new Date();
            const seconds = date.getTime();
            const milliseconds = date.getMilliseconds();
            const randNum = Math.random().toString(36);
            const uuID = _Crypto$Hash.sha1('The UUID for #' + cloned_num + ' @ ' + randNum + ' :: ' + seconds + '.' + milliseconds);
            //--
            const $clone = $el.clone().attr('id', 'clone_of__' + elID + '_' + _Utils$.create_htmid(uuID));
            //--
            $('#' + containerID).append('<div class="cloned__' + _Utils$.escape_html(elID) + '" id="' + 'clone_container__' + _Utils$.escape_html(elID) + '_' + _Utils$.escape_html(uuID) + '"><img alt="Remove" title="Remove" height="16" src="' + _Utils$.escape_html(_C$.param_ImgCloneRemove) + '" style="cursor:pointer; vertical-align:middle;" onClick="jQuery(this).parent().remove();">&nbsp;&nbsp;</div>');
            //--
            switch (String(elType)) {
                case 'text-input':
                case 'text-area':
                case 'file-input':
                    $clone.val('').appendTo('#' + 'clone_container__' + elID + '_' + _Utils$.create_htmid(uuID));
                    break;
                case 'html-element': // regular html element
                default: // other cases
                    $clone.appendTo('#' + 'clone_container__' + elID + '_' + _Utils$.create_htmid(uuID));
            } //end switch
            //--
        }; //END
        _C$.CloneElement = CloneElement; // export


        /**
         * Submit a Form by Ajax via POST and Handle the Answer
         * The function expects a json answer with the following structure: see more in framework PHP lib SmartComponents::js_ajax_replyto_html_form()
         * @example
         * // Json Structure for Answer
         * 		{
         * 			'completed': 	'DONE',
         * 			'status': 		'OK|ERROR',
         * 			'action': 		'Notification Button Text: Ok/Cancel',
         * 			'title': 		'Notification Title',
         * 			'message': 		'Notification Message HTML Content', // if empty, on success will not show any notification
         * 			'js_evcode': 	'If non-empty, the JS Code to execute on either SUCCESS or ERROR (before redirect or Div Replace) ; params: the_form_id, url, msg'
         * 			'redirect': 	'If non-empty, a redirect URL on either SUCCESS or ERROR ; on SUCCESS if message is Empty will redirect without confirmation: Growl / Dialog',
         * 			'replace_div': 	'If non-empty, an ID for a div to be replaced with content from [replace_html] on Success',
         * 			'replace_html': 'If non-empty, a HTML code to populate / replace the current Content for the [replace_div] on Success'
         * 		}
         * // #END Json Structure
         * @hint It supports simple forms or complex forms with multipart/form-data and file attachments.
         *
         * @memberof smartJ$Browser
         * @method SubmitFormByAjax
         * @static
         *
         * @param 	{String} 	the_form_id 			The form ID ; Set it to FALSE/NULL/'' (to use without a real form by emulating a form like XHR request from URL)
         * @param 	{String} 	url 					The destination URL where form or XHR request will be sent to
         * @param 	{Yes/No} 	growl 					*Optional* If 'yes' will use the Growl notifications otherwise (if 'no') will use Dialog notifications ; default is set to 'auto'
         * @param 	{JS-Code} 	evcode 					*Optional* the JS Code to execute on SUCCESS answer (before anything else) ; params: the_form_id, url, msg
         * @param 	{JS-Code} 	everrcode 				*Optional* the JS Code to execute on ERROR answer (before anything else) ; params: the_form_id, url, msg
         * @param 	{JS-Code} 	evfailcode 				*Optional* the JS Code to execute on REQUEST FAIL answer ; params: the_form_id, url, msg
         * @param 	{Boolean} 	failalertable 			*Optional* if set to TRUE will set the fail dialog to 'alertable' instead of 'auto' if alertable is available
         *
         * @fires send a form by ajax
         * @listens fire a form submit
         */
        const SubmitFormByAjax = function(the_form_id, url, growl = 'auto', evcode = null, everrcode = null, evfailcode = null, failalertable = false) { // ES6
            //--
            const _m$ = 'SubmitFormByAjax';
            //--
            growl = _Utils$.stringPureVal(growl, true); // cast to string, trim
            if (growl == '') {
                growl = 'auto';
            } else if (growl != 'no') {
                growl = 'yes';
            } //end if
            //--
            let dlg = 'auto';
            if (failalertable === true) {
                if ($.alertable) {
                    dlg = 'alertable'; // use alertable instead of dialog
                } //end if
            } //end if
            //--
            let haveGrowl = false;
            switch (growl) {
                case 'no':
                    break;
                case 'yes':
                    if (GrowlSelectType() != '') {
                        haveGrowl = true;
                    } else {
                        _p$.warn(_N$, _m$, 'WARN: Growl is required but is N/A');
                    } //end if
                    break;
                case 'auto':
                default:
                    if (GrowlSelectType() != '') {
                        haveGrowl = true;
                    } //end if
            } //end switch
            //--
            let ajax = null;
            if (!the_form_id) { // false, null or ''
                ajax = AjaxRequestFromURL(url, 'get', 'json');
            } else {
                ajax = AjaxRequestByForm(the_form_id, url, 'json');
            } //end if else
            if (ajax === null) {
                _p$.error(_N$, _m$, 'ERR: Null XHR Object on FormID:', the_form_id);
                AlertDialog('<h3>FAIL: The XHR Object is NULL !</h3>', null, 'ERROR', null, null, dlg);
                return;
            } //end if
            //--
            OverlayShow();
            //--
            ajax.done((msg) => { // {{{JQUERY-AJAX}}}
                //--
                OverlayClear();
                //--
                if ((typeof(msg) == 'object') && (msg.hasOwnProperty('completed')) && (msg.completed == 'DONE') && (msg.hasOwnProperty('status')) && ((msg.status == 'OK') || (msg.status == 'ERROR')) && (msg.hasOwnProperty('action')) && (msg.action != null) && (msg.hasOwnProperty('title')) && (msg.title != null) && (msg.hasOwnProperty('message')) && (msg.message != null) && (msg.hasOwnProperty('js_evcode')) && (msg.hasOwnProperty('redirect')) && (msg.hasOwnProperty('replace_div')) && (msg.hasOwnProperty('replace_html'))) {
                    //--
                    if (msg.status == 'OK') { // OK
                        //--
                        _Utils$.evalJsFxCode( // EV.CTX
                            _N$ + '.' + _m$ + ' (1.1)',
                            (typeof(evcode) === 'function' ?
                                () => {
                                    'use strict'; // req. strict mode for security !
                                    (evcode)(the_form_id, url, msg);
                                } :
                                () => {
                                    'use strict'; // req. strict mode for security !
                                    !!evcode ? eval(evcode) : null // already is sandboxed in a method to avoid code errors if using return ; need to be evaluated in this context because of parameters access: the_form_id, url, msg
                                }
                            )
                        );
                        //--
                        _Utils$.evalJsFxCode( // EV.CTX
                            _N$ + '.' + _m$ + ' (1.2)',
                            (typeof(msg.js_evcode) === 'function' ?
                                () => {
                                    'use strict'; // req. strict mode for security !
                                    (msg.js_evcode)(the_form_id, url, msg);
                                } :
                                () => {
                                    'use strict'; // req. strict mode for security !
                                    !!msg.js_evcode ? eval(msg.js_evcode) : null // already is sandboxed in a method to avoid code errors if using return ; need to be evaluated in this context because of parameters access: the_form_id, url, msg
                                }
                            )
                        );
                        //--
                        if ((msg.hasOwnProperty('hide_form_on_success')) && (!!msg.hide_form_on_success)) {
                            if (!!the_form_id) {
                                $('form#' + the_form_id).hide(); // {{{SYNC-FORM-SELECTOR-BY-ID}}}
                            } //end if
                        } //end if
                        //--
                        if ((msg.redirect != null) && (msg.redirect != '') && (msg.message == '')) { // if no message handle redirect here
                            RedirectDelayedToURL(msg.redirect, 250);
                        } else { // if message, the redirect will be handled by MessageNotification on closing dialog or growl
                            if ((msg.replace_div != null) && (msg.replace_div != '') && (msg.replace_html != null) && (msg.replace_html != '')) {
                                $('#' + msg.replace_div).empty().html(String(msg.replace_html));
                            } //end if
                            if (msg.message != '') {
                                MessageNotification(_Utils$.escape_html(String(msg.title)), '<img alt="ok" title="' + _Utils$.escape_html(msg.action) + '" src="' + _Utils$.escape_html(_C$.param_ImgOK) + '" align="right">' + msg.message, msg.redirect, (haveGrowl === true) ? 'yes' : 'no', (haveGrowl === true) ? 'green' : '', _C$.param_NotificationTimeOK);
                            } //end if
                            setTimeout(() => {
                                OverlayHide();
                            }, _C$.param_NotificationTimeOK + 100);
                        } //end if
                        //--
                    } else { // ERROR
                        //--
                        _Utils$.evalJsFxCode( // EV.CTX
                            _N$ + '.' + _m$ + ' (2.1)',
                            (typeof(everrcode) === 'function' ?
                                () => {
                                    'use strict'; // req. strict mode for security !
                                    (everrcode)(the_form_id, url, msg);
                                } :
                                () => {
                                    'use strict'; // req. strict mode for security !
                                    !!everrcode ? eval(everrcode) : null // already is sandboxed in a method to avoid code errors if using return ; need to be evaluated in this context because of parameters access: the_form_id, url, msg
                                }
                            )
                        );
                        //--
                        _Utils$.evalJsFxCode( // EV.CTX
                            _N$ + '.' + _m$ + ' (2.2)',
                            (typeof(msg.js_evcode) === 'function' ?
                                () => {
                                    'use strict'; // req. strict mode for security !
                                    (msg.js_evcode)(the_form_id, url, msg);
                                } :
                                () => {
                                    'use strict'; // req. strict mode for security !
                                    !!msg.js_evcode ? eval(msg.js_evcode) : null // already is sandboxed in a method to avoid code errors if using return ; need to be evaluated in this context because of parameters access: the_form_id, url, msg
                                }
                            )
                        );
                        //-- on error, the redirect will only be handled by MessageNotification on closing dialog or growl, does not count if have message or not, an error must be display
                        MessageNotification('* ' + _Utils$.escape_html(msg.title), '<img alt="fail" title="' + _Utils$.escape_html(msg.action) + '" src="' + _Utils$.escape_html(_C$.param_ImgNotOK) + '" align="right">' + msg.message, msg.redirect, (haveGrowl === true) ? 'yes' : 'no', (haveGrowl === true) ? 'red' : '', _C$.param_NotificationTimeERR);
                        setTimeout(() => {
                            OverlayHide();
                        }, _C$.param_NotificationTimeERR + 100);
                        //--
                    } //end if else
                    //--
                } else {
                    //--
                    _p$.warn(_N$, _m$, 'WARN: Invalid Data Object Format');
                    MessageNotification('* ERR', '<img alt="fail" src="' + _Utils$.escape_html(_C$.param_ImgNotOK) + '" align="right">' + 'Invalid Form Submit Response: Unexpected Data Object Format', null, (haveGrowl === true) ? 'yes' : 'no', (haveGrowl === true) ? 'pink' : '', _C$.param_NotificationTimeERR);
                    setTimeout(() => {
                        OverlayHide();
                    }, _C$.param_NotificationTimeERR + 500);
                    //--
                    _Utils$.evalJsFxCode( // EV.CTX
                        _N$ + '.' + _m$ + ' (3)',
                        (typeof(evfailcode) === 'function' ?
                            () => {
                                'use strict'; // req. strict mode for security !
                                (evfailcode)(the_form_id, url, msg);
                            } :
                            () => {
                                'use strict'; // req. strict mode for security !
                                !!evfailcode ? eval(evfailcode) : null // already is sandboxed in a method to avoid code errors if using return ; need to be evaluated in this context because of parameters access: the_form_id, url, msg
                            }
                        )
                    );
                    //--
                } //end if else
                //--
            }).fail((msg) => {
                //--
                OverlayClear();
                //--
                AlertDialog(
                    '<h4>HTTP&nbsp;Status: ' + _Utils$.escape_html(msg.status) + _Utils$.escape_html(HTTP_STATUS_CODES[String(msg.status)] ? ' ' + HTTP_STATUS_CODES[String(msg.status)] : (msg.statusText ? msg.statusText : 'Unknown')) + '</h4><br>' + '\n' + '<h5>XHR Server Response NOT Validated ...</h5>' + ((msg.status == 401) ? '<h6>If the auth credentials are valid and still getting this message do a full page refresh in the browser and try again, it could be a re-auth issue !</h6>' : ''), // + '\n' + msg.responseText (... not safe to display responseText, it may contain unattended javascripts and external css styles)
                    (typeof(evfailcode) === 'function' ?
                        () => {
                            'use strict'; // req. strict mode for security !
                            (evfailcode)(the_form_id, url, msg);
                        } :
                        () => {
                            'use strict'; // req. strict mode for security !
                            !!evfailcode ? eval(evfailcode) : null // already is sandboxed in a method to avoid code errors if using return ; need to be evaluated in this context because of parameters access: the_form_id, url, msg
                        }
                    ),
                    'FAIL',
                    null,
                    null,
                    dlg
                );
                setTimeout(() => {
                    OverlayHide();
                }, _C$.param_NotificationTimeERR + 1000);
                //--
            });
            //--
        }; //END
        _C$.SubmitFormByAjax = SubmitFormByAjax; // export


        /**
         * Create an Ajax XHR Request (POST) by Form
         * It supports simple forms or complex forms with multipart/form-data and file attachments.
         *
         * @memberof smartJ$Browser
         * @method AjaxRequestByForm
         * @static
         *
         * @param 	{String} 	the_form_id 			The element ID of the form to be create the Ajax XHR Request for
         * @param 	{String} 	url 					The URL to send form to via POST
         * @param 	{Enum} 		data_type 				The type of Data served back by the Request: json | html | text
         * @return 	{Object} 							The Ajax XHR Request Object ; The following methods must be bind to the object and redefined:
         *
         * @example
         * let ajax = smartJ$Browser.AjaxRequestByForm('my-form', 'http(s);//url-to', 'json')
         * 		.done: 		function(msg) {}
         * 		.fail: 		function(msg) {}
         * 		.always: 	function(msg) {}
         */
        const AjaxRequestByForm = function(the_form_id, url, data_type) { // ES6
            //--
            const _m$ = 'AjaxRequestByForm';
            //--
            the_form_id = _Utils$.stringPureVal(the_form_id, true); // cast to string, trim
            url = _Utils$.stringPureVal(url, true); // cast to string, trim
            //--
            let $form = null;
            let ajax = null;
            let data = '';
            //--
            if (the_form_id != '') {
                $form = $('form#' + the_form_id); // {{{SYNC-FORM-SELECTOR-BY-ID}}}
                if (typeof($form.get(0)) == 'undefined') {
                    _p$.error(_N$, _m$, 'ERR: Form [' + the_form_id + '] was not found');
                    return null;
                } //end if
                if (url == '') { // undef tests also for null
                    url = _Utils$.stringPureVal($form.attr('action'), true); // try to get form action if URL is empty ; cast to string, trim
                } //end if
            } //end if
            if ((url == '') || (url == '#')) {
                _p$.error(_N$, _m$, 'ERR: Invalid or Empty URL');
                return null;
            } //end if
            //--
            if (the_form_id == '') {
                //--
                ajax = AjaxRequestFromURL(url, 'GET', data_type, '');
                //--
            } else {
                //--
                const fMethod = _Utils$.stringPureVal($form.attr('method'), true); // cast to string, trim
                const fEncType = _Utils$.stringPureVal($form.attr('enctype'), true); // cast to string, trim
                let isMultipart = false;
                if ((fMethod.toLowerCase() == 'post') && (fEncType.toLowerCase() == 'multipart/form-data')) {
                    let haveFiles = $form.find('input:file');
                    if (typeof(haveFiles) != 'undefined') {
                        if (typeof(haveFiles.get(0)) != 'undefined') {
                            isMultipart = true; // have files
                        } //end if
                    } //end if
                } //end if
                //--
                if (isMultipart !== true) {
                    //--
                    data = $form.serialize(); // no files detected use serialize
                    ajax = AjaxRequestFromURL(url, 'POST', data_type, data);
                    //--
                } else {
                    //--
                    try {
                        //--
                        data = new FormData($form.get(0)); // data.append('__fix', '...multipart form fix for arr vars...'); // workarround for IE10/11 bugfix with array variables, after array of vars a non-array var must be to avoid corruption: http://blog.yorkxin.org/posts/2014/02/06/ajax-with-formdata-is-broken-on-ie10-ie11/
                        ajax = AjaxPostMultiPartToURL(url, data_type, data);
                        //--
                    } catch (err) { // it must alert to anounce the user
                        //--
                        data = $form.serialize(); // no files detected use serialize
                        ajax = AjaxRequestFromURL(url, 'POST', data_type, data);
                        //--
                        const warnMsg = 'ERR: ' + _m$ + ' FormData Object Failed. File Attachments were NOT sent ! Try to upgrade / change your browser. It may not support HTML5 File Uploads.';
                        _p$.error(_N$, warnMsg, ' # Form:', the_form_id);
                        AlertDialog(_Utils$.escape_html(warnMsg), null, 'Form: ' + the_form_id);
                        //--
                    } //end try catch
                    //--
                } //end if else
                //--
            } //end if
            //--
            return ajax;
            //--
            /* the below functions must be assigned later to avoid execution here {{{SYNC-JQUERY-AJAX-EVENTS}}}
            let ajax = smartJ$Browser.AjaxRequestByForm(...)
            ajax.done(function(data, textStatus, jqXHR) {}); // instead of .success() (which is deprecated or removed from newest jQuery)
            ajax.fail(function(jqXHR, textStatus, errorThrown) {}); // instead of .error() (which is deprecated or removed from newest jQuery)
            ajax.always(function(data|jqXHR, textStatus, jqXHR|errorThrown) {}); // *optional* instead of .complete() (which is deprecated or removed from newest jQuery)
            */
            //--
        }; //END
        _C$.AjaxRequestByForm = AjaxRequestByForm; // export


        /**
         * Create an Ajax XHR Request (POST) using multipart/form-data type to bse used with file attachments.
         * Instead using it directly is better to use:
         * 		smartJ$Browser.AjaxRequestByForm(); 		// basic, will detect the form type if must use multipart/form-data + attachments (if any)
         * or even much better use:
         * 		smartJ$Browser.SubmitFormByAjax(); 	// advanced, will handle the XHR form request and the answer
         * @hint NOTICE: It does not send the 'contentType' to allow new FormData send multipart form if necessary ...
         *
         * @private : internal development only
         *
         * @memberof smartJ$Browser
         * @method AjaxPostMultiPartToURL
         * @static
         *
         * @param 	{String} 	y_url 					The URL to send form to via method POST
         * @param 	{Enum} 		y_data_type 			The type of Data served back by the Request: json | jsonp | script | html | xml | text
         * @param 	{Mixed} 	y_data_formData 		The *special* serialized form data as object using: new FormData(jQuery('#'+the_form_id).get(0)) to support attachments or string via serialize() such as: '&var1=value1&var2=value2'
         * @param 	{Boolean} 	y_withCredentials 		*Optional* Send Credentials (CORS Only) ; default is FALSE ; set to TRUE to send XHR Credentials
         * @return 	{Object} 							The Ajax XHR Request Object ; The following methods must be bind to the object and redefined:
         *
         * @example
         * 		.done: 		function(msg) {}
         * 		.fail: 		function(msg) {}
         * 		.always: 	function(msg) {}
         */
        const AjaxPostMultiPartToURL = function(y_url, y_data_type, y_data_formData, y_withCredentials = false) { // ES6
            //--
            const _m$ = 'AjaxPostMultiPartToURL';
            //--
            y_url = _Utils$.stringPureVal(y_url, true); // cast to string, trim
            if ((y_url == '') || (y_url == '#')) {
                _p$.error(_N$, _m$, 'ERR: Empty URL');
                return null;
            } //end if
            //--
            y_data_type = getValidjQueryAjaxType(y_data_type);
            //--
            let ajxOpts = { // not const, below will change
                //--
                async: true,
                //	cache: 				false, // by default is true ; let it be set globally via ajaxSetup
                //	timeout: 			0, // by default is zero ; let it be set globally via ajaxSetup
                type: 'POST',
                url: String(y_url),
                //--
                contentType: false,
                processData: false,
                //--
                data: y_data_formData, // String or Object
                dataType: String(y_data_type), // json, jsonp, script, html, xml or text
                //--
            };
            //--
            if (y_withCredentials === true) {
                ajxOpts.xhrFields = {
                    withCredentials: true // allow send credentials (CORS): FALSE / TRUE
                };
            } //end if
            //--
            return $.ajax(ajxOpts);
            //--
            /* the below functions can be assigned later to avoid execution here {{{SYNC-JQUERY-AJAX-EVENTS}}}
            let ajax = smartJ$Browser.AjaxPostMultiPartToURL(...)
            ajax.done(function(data, textStatus, jqXHR) {}); // instead of .success() (which is deprecated or removed from newest jQuery)
            ajax.fail(function(jqXHR, textStatus, errorThrown) {}); // instead of .error() (which is deprecated or removed from newest jQuery)
            ajax.always(function(data|jqXHR, textStatus, jqXHR|errorThrown) {}); // *optional* instead of .complete() (which is deprecated or removed from newest jQuery)
            */
            //--
        }; //END
        _C$.AjaxPostMultiPartToURL = AjaxPostMultiPartToURL; // export


        /**
         * Create a general purpose Ajax XHR Request (GET/POST) with Optional support for Authentication and Extra Headers
         * For creating an Ajax XHR Request to be used with HTML Forms use:
         * 		smartJ$Browser.AjaxRequestByForm(); 		// basic, will detect the form type if must use multipart/form-data + attachments (if any)
         * or even much better use:
         * 		smartJ$Browser.SubmitFormByAjax(); 	// advanced, will handle the XHR form request and the answer
         * @hint It is NOT intended to be used with HTML forms that may contain multipart/form-data and file attachments or not.
         *
         * @memberof smartJ$Browser
         * @method AjaxRequestFromURL
         * @static
         *
         * @param 	{String} 	y_url 						The URL to send the Request to
         * @param 	{Enum} 		y_method 					The Request Method: GET / POST / PUT / DELETE / OPTIONS / HEAD
         * @param 	{Enum} 		y_data_type 				The type of Data served back by the Request: json | jsonp | script | html | xml | text
         * @param 	{Mixed} 	y_data_arr_or_serialized 	The Data to be sent: a serialized string via serialize() such as: '&var1=value1&var2=value2' or an associative array (Object) as: { var1: "value1", var2: "value2" }
         * @param 	{String} 	y_AuthUser 					*Optional* The Authentication UserName (if custom Authentication need to be used)
         * @param 	{String} 	y_AuthPass 					*Optional* The Authentication Password (if custom Authentication need to be used)
         * @param 	{Object} 	y_Headers 					*Optional* Extra Headers to be sent with the Request ; Default is NULL (ex: { 'X-Head1': 'Test1', ... })
         * @param 	{Boolean} 	y_withCredentials 			*Optional* Send Credentials (CORS Only) ; default is FALSE ; set to TRUE to send XHR Credentials
         * @return 	{Object} 								The Ajax XHR Request Object ; The following methods must be bind to the object and redefined:
         *
         * @example
         * let ajax = smartJ$Browser.AjaxRequestFromURL('http(s);//url', 'POST', 'json', '&var1=value1&var2=value2')
         * 		.done: 		function(msg) {}
         * 		.fail: 		function(msg) {}
         * 		.always: 	function(msg) {}
         */
        const AjaxRequestFromURL = function(y_url, y_method, y_data_type, y_data_arr_or_serialized, y_AuthUser = null, y_AuthPass = null, y_Headers = null, y_withCredentials = false) { // ES6
            //--
            const _m$ = 'AjaxRequestFromURL';
            //--
            y_url = _Utils$.stringPureVal(y_url, true); // cast to string, trim
            if (y_url == '') {
                _p$.error(_N$, _m$, 'ERR: Empty URL');
                return null;
            } //end if
            //--
            y_method = _Utils$.stringPureVal(y_method, true).toUpperCase(); // cast to string, trim + uppercase
            switch (y_method) {
                case 'OPTIONS':
                case 'DELETE':
                case 'PUT':
                case 'HEAD':
                case 'POST':
                case 'GET':
                    break;
                default:
                    y_method = 'GET';
            } //end switch
            //--
            y_data_type = getValidjQueryAjaxType(y_data_type);
            //--
            if (typeof(y_data_arr_or_serialized) != 'object') {
                y_data_arr_or_serialized = _Utils$.stringPureVal(y_data_arr_or_serialized); // cast to string, do not trim
            } //end if
            //--
            let the_headers = {}; // default
            if (typeof(y_Headers) == 'object') {
                the_headers = y_Headers;
            } //end if
            //--
            let the_user = '';
            let the_pass = '';
            y_AuthUser = _Utils$.stringPureVal(y_AuthUser, true); // cast to string, trim
            y_AuthPass = _Utils$.stringPureVal(y_AuthPass); // cast to string, do not trim
            if (y_AuthUser && y_AuthPass) {
                the_user = String(y_AuthUser);
                the_pass = String(y_AuthPass);
            } //end if
            //--
            let ajxOpts = {
                //--
                async: true,
                //	cache: 				false, // by default is true ; let it be set globally via ajaxSetup
                //	timeout: 			0, // by default is zero ; let it be set globally via ajaxSetup
                type: String(y_method),
                url: String(y_url),
                //--
                headers: the_headers, // extra headers object or NULL
                username: String(the_user), // auth user name or ''
                password: String(the_pass), // auth user pass or ''
                //--
                data: y_data_arr_or_serialized, // it can be a serialized STRING as: '&var1=value1&var2=value2' or OBJECT as: { var1: "value1", var2: "value2" }
                dataType: String(y_data_type) // json, jsonp, script, html, xml or text
                //--
            };
            //--
            if (y_withCredentials === true) {
                ajxOpts.xhrFields = {
                    withCredentials: true // allow send credentials (CORS): FALSE / TRUE
                };
            } //end if
            //--
            return $.ajax(ajxOpts);
            //--
            /* [Sample Implementation:] {{{SYNC-JQUERY-AJAX-EVENTS}}}
            let ajax = smartJ$Browser.AjaxRequestFromURL(...);
            // {{{JQUERY-AJAX}}} :: the below functions: done() / fail() / always() must be assigned on execution because they are actually executing the ajax request and the AjaxRequestFromURL() just creates the request object !
            ajax.done(function(data, textStatus, jqXHR) { // instead of .success() (which is deprecated or removed from newest jQuery)
            	// code for done
            }).fail(function(jqXHR, textStatus, errorThrown) { // instead of .error() (which is deprecated or removed from newest jQuery)
            	// code for fail
            }).always(function(data|jqXHR, textStatus, jqXHR|errorThrown) { // *optional* instead of .complete() (which is deprecated or removed from newest jQuery)
            	// code for always
            });
            */
            //--
        }; //END
        _C$.AjaxRequestFromURL = AjaxRequestFromURL; // export


        /**
         * Loads the contents for a Div (or other compatible) HTML Element(s) by Ajax using a GET / POST Ajax Request
         * @hint It is intended to simplify populating a Div (or other compatible) HTML Element(s) with content(s) by Ajax Requests.
         *
         * @memberof smartJ$Browser
         * @method LoadElementContentByAjax
         * @static
         *
         * @param 	{String} 	y_elemID 					The ID of the Div (or other compatible) HTML Element(s) to bind to
         * @param 	{String} 	y_img_loader 				If non-empty, a pre-loader image that will be displayed while loading ; if set to TRUE will use smartJ$Browser.param_LoaderImg;
         * @param 	{String} 	y_url 						The URL to send the Request to
         * @param 	{Enum} 		y_method 					The Request Method: GET / POST
         * @param 	{Enum} 		y_data_type 				The type of Data served back by the Request: html | text | json (div_content_html)
         * @param 	{Mixed} 	y_data_arr_or_serialized 	The Data to be sent: a serialized string via serialize() such as: '&var1=value1&var2=value2' or an associative array as: { var1: "value1", var2: "value2" }
         * @param 	{Boolean} 	y_replace 					*Optional* Default is FALSE ; If set to TRUE will use the jQuery method .replaceWith() instead of .empty().html()
         * @param 	{Mixed} 	y_notifyerr 				*Optional* Default is NULL ; If set to TRUE or FALSE will override the general setting for notification error ; if TRUE on Error loading the content will display a message Dialog instead of logging the errros to console
         *
         * @fires load the div content by ajax in the background and if successful will replace the div content with what comes by ajax
         */
        const LoadElementContentByAjax = function(y_elemID, y_img_loader, y_url, y_method, y_data_type, y_data_arr_or_serialized, y_replace = false, y_notifyerr = null) {
            //--
            const _m$ = 'LoadElementContentByAjax';
            //--
            y_elemID = _Utils$.stringPureVal(y_elemID, true); // cast to string, trim
            y_elemID = _Utils$.create_htmid(y_elemID);
            if (y_elemID == '') {
                _p$.error(_N$, _m$, 'ERR: Empty Element ID');
                return;
            } //end if
            //--
            if (y_img_loader === true) {
                y_img_loader = _C$.param_LoaderImg;
            } //end if
            y_img_loader = _Utils$.stringPureVal(y_img_loader, true); // cast to string, trim
            if (y_img_loader != '') {
                if ($('#' + y_elemID + '__' + _Utils$.create_htmid(_m$)).length == 0) {
                    $('#' + y_elemID).prepend('<span id="' + _Utils$.escape_html(y_elemID) + '__' + _Utils$.create_htmid(_m$) + '"><img src="' + _Utils$.escape_html(y_img_loader) + '" title="Loading ..." alt="Loading ..."></span><br>');
                } //end if
            } //end if
            //--
            let ajax = AjaxRequestFromURL(y_url, y_method, y_data_type, y_data_arr_or_serialized);
            //--
            ajax.done((msg) => { // {{{JQUERY-AJAX}}}
                let divContent = '';
                if (y_data_type === 'json') {
                    if (msg.hasOwnProperty('div_content_html')) {
                        divContent = String(msg.div_content_html);
                    } //end if
                } else { // text | html
                    divContent = String(msg);
                } //end if else
                if (y_replace === true) {
                    $('#' + y_elemID).replaceWith(divContent);
                } else {
                    $('#' + y_elemID).empty().html(divContent);
                } //end if else
            }).fail((msg) => {
                let notifyErr = !!_C$.param_NotifyLoadError;
                if ((y_notifyerr === true) || (y_notifyerr === false)) {
                    notifyErr = !!y_notifyerr;
                } //end if
                const errDetails = 'ElementID: ' + y_elemID + ' ; ' + 'URL: ' + y_url + ' ; ' + 'Method: ' + y_method + ' ; ' + 'DataType: ' + y_data_type;
                if (notifyErr === true) {
                    GrowlNotificationAdd('HTTP&nbsp;Status: ' + _Utils$.escape_html(msg.status) + _Utils$.escape_html(HTTP_STATUS_CODES[String(msg.status)] ? ' ' + HTTP_STATUS_CODES[String(msg.status)] : (msg.statusText ? msg.statusText : 'Unknown')), 'FAIL: Invalid Server Response for: ' + _Utils$.escape_html(_m$) + '<br>' + '<b><i>Details:</i></b><br>' + _Utils$.nl2br(_Utils$.escape_html(_Utils$.stringReplaceAll(' ; ', '\n', errDetails))), _C$.param_ImgNotOK, 0, true, 'dark'); // , msg.responseText
                } else {
                    _p$.error(_N$, _m$, 'ERR: Invalid Server Response', 'HTTP Status Code:', msg.status, ';', errDetails); // , msg.responseText
                } //end if else
                $('#' + y_elemID).empty().html(''); // clear
            });
            //--
        }; //END
        _C$.LoadElementContentByAjax = LoadElementContentByAjax;


        /**
         * Scroll down a browser window by reference.
         * It will focus the referenced browser window first.
         *
         * @memberof smartJ$Browser
         * @method windwScrollDown
         * @static
         * @arrow
         *
         * @param 	{Object} 	wnd 		The window (reference) object
         * @param 	{Integer} 	offset 		The offset in pixels to scroll down ; use -1 to scroll to the end of document
         *
         * @fires Scroll Down a Browser window
         */
        const windwScrollDown = function(wnd, offset) { // ES6
            //--
            let scrollY = _Utils$.format_number_int(parseInt(offset));
            if (scrollY < 0) { // if offset is -1 will go to end
                scrollY = _Utils$.format_number_int(parseInt($(document).height()));
                if (scrollY < 0) {
                    scrollY = 0;
                } //end if
            } //end if
            //--
            try {
                wnd.scrollBy(0, scrollY);
            } catch (err) {} // just in case
            //--
        }; //END
        _C$.windwScrollDown = windwScrollDown; // export


        /**
         * Trigger a function when you scroll the page to a specific target element
         * @hint It may be used to make an infinite scroll behaviour to load more content on page when page reach the bottom end
         *
         * @memberof smartJ$Browser
         * @method WayPoint
         * @static
         *
         * @param 	{String} 	elSelector 					The Element Selector ; example: '#elem-id' or '.elem-class' (interpretable by jQuery)
         * @param 	{JS-Code} 	evcode 						the JS Code to execute on complete
         *
         * @fires trigger the function that is set (2nd param)
         * @listens the target element (1st param) to be scrolled in the visible area
         */
        const WayPoint = function(elSelector, evcode) {
            //--
            const _m$ = 'WayPoint';
            //--
            elSelector = _Utils$.stringPureVal(elSelector, true); // cast to string, trim
            if ((elSelector == '') || (elSelector == '.') || (elSelector == '#')) {
                _p$.error(_N$, _m$, 'ERR: invalid selector:', elSelector);
                return;
            } //end if
            if (typeof(evcode) !== 'function') {
                evcode = _Utils$.stringPureVal(evcode); // cast to string
                if (_Utils$.stringTrim(evcode) == '') { // undef tests also for null
                    _p$.error(_N$, _m$, 'ERR: empty evcode');
                    return;
                } //end if
            } //end if
            //--
            let scrollTrigger = true;
            const $w = $(window);
            //--
            $(window).scroll(() => {
                //--
                if (!scrollTrigger) {
                    return;
                } //end if
                //--
                const $e = $(String(elSelector));
                if ((!$e.length) || $e.is(':hidden')) {
                    return false;
                } //end if
                //--
                const wt = $w.scrollTop(),
                    wb = wt + $w.height(),
                    et = $e.offset().top,
                    eb = et + $e.height(),
                    th = 0; // treshold
                const shouldLoad = (eb >= wt - th && et <= wb + th);
                //--
                if (shouldLoad) { // scrolled to the target
                    //--
                    scrollTrigger = false;
                    //--
                    _Utils$.evalJsFxCode( // EV.CTX
                        _N$ + '.' + _m$,
                        (typeof(evcode) === 'function' ?
                            () => {
                                'use strict'; // req. strict mode for security !
                                (evcode)($e, elSelector);
                            } :
                            () => {
                                'use strict'; // req. strict mode for security !
                                !!evcode ? eval(evcode) : null // already is sandboxed in a method to avoid code errors if using return ; need to be evaluated in this context because of parameters access: $e, elSelector
                            }
                        )
                    );
                    //--
                } //end if
                //--
            });
            //--
        }; //END
        _C$.WayPoint = WayPoint; // export


        /**
         * Create a virtual file download from Javascript
         *
         * @memberof smartJ$Browser
         * @method VirtualFileDownload
         * @static
         *
         * @param 	{String} 	data 						The content of the file to be downloaded
         * @param 	{String} 	fileName 					The file name (ex: 'file.txt')
         * @param 	{String} 	mimeType 					The mime type (ex: 'text/plain')
         * @param 	{String} 	charset 					*Optional* ; Default is NULL, will use the value from param_Charset or if not set will use 'UTF-8' ; set it to FALSE if binary is set to TRUE ; The character set of the file content (ex: 'UTF-8' for text OR FALSE for binary)
         * @param 	{Boolean} 	binary 						*Optional* ; Default is FALSE ; if TRUE will use no character set, but binary ASCII
         *
         * @fires emulates a file download
         */
        const VirtualFileDownload = function(data, fileName, mimeType, charset = null, binary = false) {
            //--
            const _m$ = 'VirtualFileDownload';
            //--
            data = _Utils$.stringPureVal(data); // cast to string, don't trim ! need to preserve the value
            //--
            fileName = _Utils$.stringPureVal(fileName, true); // cast to string, trim
            if (!fileName) {
                fileName = 'file.none';
            } //end if
            //--
            mimeType = _Utils$.stringPureVal(mimeType, true); // cast to string, trim
            if (!mimeType) {
                mimeType = 'application/octet-stream';
            } //end if
            //--
            if (charset === null) {
                charset = String(_C$.param_Charset);
            } //end if else
            charset = _Utils$.stringPureVal(charset, true); // cast to string, trim
            //--
            if (binary === true) {
                charset = '';
            } else {
                binary = false;
                if (charset == '') { // fallback
                    charset = 'UTF-8'; // default
                } //end if
            } //end if
            //--
            let link = null;
            try {
                link = document.createElement('a');
                if (link) {
                    link.href = String('data:' + String(mimeType) + (charset ? ';charset=' + charset : '') + ';base64,' + _Ba$e64.encode(data, binary));
                    link.target = '_blank';
                    link.setAttribute('download', String(fileName));
                    document.body.appendChild(link);
                    link.style = 'display:none';
                    link.click();
                } //end if
            } catch (err) {
                link = false;
                _p$.error(_N$, _m$, 'ERR: Create Failed:', err);
            } //end try catch
            //--
            if (link) {
                setTimeout(() => {
                    try {
                        document.body.removeChild(link);
                    } catch (e) {
                        _p$.warn(_N$, _m$, 'WARN: Remove Failed:', e);
                    } //end try catch
                }, 250);
            } //end if
            //--
        }; //END
        _C$.VirtualFileDownload = VirtualFileDownload; // export


        /**
         * Create a virtual image upload handler
         * Requires in HTML:
         * 		(#1) an input type file (can be any of visible or hidden/triggered by a button click)
         * 		(#2) an image preview container (div)
         *
         * @memberof smartJ$Browser
         * @method VirtualImageUploadHandler
         * @static
         *
         * @param 	{String} 	inputUpldFileId 			The HTML-Id of the input type file (#1)
         * @param 	{String} 	previewPlaceholderId 		The HTML-Id of the preview container (#2)
         * @param 	{Decimal} 	imgQuality 					The image quality (0.1 ... 1)
         * @param 	{Decimal} 	imgMaxResizeMBSize 			The max resized image size in MB (0.01 ... 2) ; Default is 0.1
         * @param 	{Integer} 	imgMaxResizeW 				The max resize width of the image (if the image width is lower will be not resized)
         * @param 	{Integer} 	imgMaxResizeH 				The max resize height of the image (if the image height is lower will be not resized)
         * @param 	{CallBack} 	fxDone 						A callback function(imgDataURL, w, h, isSVG, type, size, name) { ... } for done
         * @param 	{Boolean} 	clearPreview 				If FALSE will not clear the preview container after done; Default is TRUE
         * @param 	{Integer} 	widthPreview 				The Preview Width to set if not clearing the preview ; if not defined, full resized image width will be used (imgMaxResizeW)
         * @param 	{Integer} 	heightPreview 				The Preview Height to set if not clearing the preview ; if not defined, full resized image height will be used (imgMaxResizeH)
         * @param 	{Boolean} 	preserveGifs 				If TRUE will not process GIFS (which will be converted to PNG if processed via canvas) ; it may be used to avoid break animated Gifs
         */
        const VirtualImageUploadHandler = function(inputUpldFileId, previewPlaceholderId, imgQuality, imgMaxResizeMBSize, imgMaxResizeW, imgMaxResizeH, fxDone, clearPreview, widthPreview, heightPreview, preserveGifs) { // ES6
            //--
            const _m$ = 'VirtualImageUploadHandler';
            const txtWarn = 'Image Uploader WARNING: ';
            //--
            inputUpldFileId = _Utils$.stringPureVal(inputUpldFileId, true); // cast to string, trim
            inputUpldFileId = _Utils$.create_htmid(inputUpldFileId);
            if (inputUpldFileId == '') {
                _p$.error(_N$, _m$, 'ERR:', 'Invalid uploadInput ID');
                return;
            } //end if
            //--
            previewPlaceholderId = _Utils$.stringPureVal(previewPlaceholderId, true); // cast to string, trim
            previewPlaceholderId = _Utils$.create_htmid(previewPlaceholderId);
            if (previewPlaceholderId == '') {
                _p$.error(_N$, _m$, 'ERR:', 'Invalid uploadPreview ID');
                return;
            } //end if
            //--
            if (!imgQuality) {
                imgQuality = 0.85; // jpeg quality set at 0.85 by default, if not specified
            } //end if
            imgQuality = _Utils$.format_number_float(imgQuality);
            if (imgQuality < 0.1) {
                imgQuality = 0.1;
            } else if (imgQuality > 1) {
                imgQuality = 1;
            } //end if else
            //--
            if (!imgMaxResizeMBSize) {
                imgMaxResizeMBSize = 0.1;
            } //end if
            imgMaxResizeMBSize = _Utils$.format_number_float(imgMaxResizeMBSize);
            if (imgMaxResizeMBSize < 0.01) {
                imgMaxResizeMBSize = 0.01; // min 0.01 MB
            } else if (imgMaxResizeMBSize > 2) {
                imgMaxResizeMBSize = 2; // max 2MB
            } //end if else
            //--
            imgMaxResizeW = _Utils$.format_number_int(imgMaxResizeW);
            if (imgMaxResizeW <= 0) {
                imgMaxResizeW = 800;
            } //end if
            //--
            imgMaxResizeH = _Utils$.format_number_int(imgMaxResizeH);
            if (imgMaxResizeH <= 0) {
                imgMaxResizeH = 600;
            } //end if
            //--
            if (clearPreview !== false) {
                clearPreview = true;
            } //end if
            //--
            widthPreview = _Utils$.format_number_int(widthPreview);
            if (widthPreview <= 0) {
                widthPreview = imgMaxResizeW;
            } //end if
            //--
            heightPreview = _Utils$.format_number_int(heightPreview);
            if (heightPreview <= 0) {
                heightPreview = heightPreview;
            } //end if
            //--
            if (preserveGifs !== true) {
                preserveGifs = false;
            } //end if
            //--
            if (typeof(fxDone) != 'function') {
                fxDone = null;
            } //end if
            //--
            const $upldr = $('#' + inputUpldFileId);
            const $imgpw = $('#' + previewPlaceholderId);
            //--
            $upldr.on('change', () => {
                //--
                const the_file = $upldr[0].files[0]; // object
                if (!the_file) {
                    $upldr.val('');
                    _p$.error(_N$, _m$, 'ERR:', 'Invalid File Uploader');
                    return;
                } //end if
                //--
                let the_name_of_file = String(the_file.name || '');
                let the_type_of_file = String(the_file.type || '').toLowerCase();
                let the_size_of_file = _Utils$.format_number_int(the_file.size, false);
                //--
                const the_filter = /^(image\/svg\+xml|image\/webp|image\/jpeg|image\/png|image\/gif)$/i;
                if (!the_filter.test(the_type_of_file)) { // check file type
                    $upldr.val('');
                    $imgpw.text(txtWarn + 'Invalid File Type - Only SVG / JPEG / WEBP / PNG or GIF Images are allowed): ' + the_type_of_file);
                    return;
                } //end if
                //--
                /* it has been fixed between Firefox 78 and 91, now works and WEBP is a standard ! It should work in all browsers ...
                if(the_type_of_file == 'image/webp') {
                	if(!window.chrome) { // TODO: Remove it after Firefox can handle canvas.toDataURL("image/webp") ; currently there is a bug in Firefox that instead producing a webp image it falls back to png and gives data:image/png;base64 https://bugzilla.mozilla.org/show_bug.cgi?id=1559743
                		the_type_of_file = 'image/jpeg'; // {{{SYNC-JS-CANVAS-CANNOT-HANDLE-WEBP}}} ; fix: currently only Chrome supports image/webp for Canvas DataURL ; https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL
                	} //end if
                } //end if
                */
                //--
                if (the_size_of_file < 43) { // check file size: min WEBP or JPEG size is 134 bytes ; min PNG size is unknown ; min GIF size is 43 bytes
                    $upldr.val('');
                    $imgpw.text(txtWarn + 'Invalid File Size - Image size is empty or too small');
                    return;
                } else if (the_size_of_file > (1024 * 1024 * 32)) { // check uploaded max file size (<32MB)
                    $upldr.val('');
                    $imgpw.text(txtWarn + 'Invalid File Size - Image is larger than 32MB');
                    return;
                } //end if
                //--
                const imgRemoveBtn = '<div title="Remove the Image" style="width:20px; height:20px; line-height:20px; font-size:15px; font-weight:bold; color:#111111; cursor:pointer;" onClick="jQuery(\'#' + _Utils$.escape_js(inputUpldFileId) + '\').val(\'\'); jQuery(\'#' + _Utils$.escape_js(previewPlaceholderId) + '\').html(\'\');">&times;</div>';
                //--
                $imgpw.html(imgRemoveBtn);
                //--
                try {
                    //--
                    const the_frd = new FileReader();
                    //--
                    the_frd.onloadend = () => {
                        $imgpw.append('<img id="uxm-img-uploader-result-img" src="' + _Utils$.escape_html(the_frd.result) + '" style="max-width:' + _Utils$.escape_html(imgMaxResizeW) + 'px; max-height:' + _Utils$.escape_html(imgMaxResizeH) + 'px; width:auto !important; height:auto !important;">');
                        setTimeout(() => {
                                const isSVG = (String(the_type_of_file).toLowerCase() === 'image/svg+xml') ? true : false;
                                const img = $('#uxm-img-uploader-result-img');
                                let isOK = false;
                                let w = Math.round(img.width()) || 1;
                                let h = Math.round(img.height()) || 1;
                                let isPreservedGif = false;
                                if (preserveGifs === true) {
                                    if (the_type_of_file === 'image/gif') {
                                        isPreservedGif = true;
                                    } //end if
                                } //end if
                                //_p$.log(_N$, _m$, 'Metadata:', w, h, isSVG);
                                if (isSVG || isPreservedGif) {
                                    if (String(the_frd.result).length <= (1024 * 1024 * imgMaxResizeMBSize)) {
                                        isOK = true;
                                        if (typeof(fxDone) == 'function') {
                                            try {
                                                fxDone(String(the_frd.result), w, h, isSVG, String(the_type_of_file), String(the_frd.result).length, String(the_name_of_file));
                                            } catch (e) {
                                                $upldr.val('');
                                                _p$.error(_N$, _m$, 'ERR:', 'SVG CallBack Failed:', e);
                                                return;
                                            } //end try catch
                                        } //end if
                                    } else {
                                        $upldr.val('');
                                        $imgpw.text(txtWarn + 'Size is higher than allowed size: ' + String(the_frd.result).length + ' Bytes');
                                    } //end if else
                                    if (isOK) {
                                        $imgpw.empty().html('');
                                        if (!clearPreview) {
                                            $imgpw.html(String(imgRemoveBtn) + "\n" + '<img id="uxm-img-uploader-result-img" src="' + _Utils$.escape_html(the_frd.result) + '" style="max-width:' + _Utils$.escape_html(widthPreview) + 'px; max-height:' + _Utils$.escape_html(heightPreview) + 'px; width:auto !important; height:auto !important;">');
                                        } //end if
                                    } //end if
                                } else {
                                    $('#uxm-img-uploader-result-img').remove();
                                    $imgpw.append('<canvas id="uxm-img-uploader-result-cnvs" width="' + _Utils$.escape_html(w) + '" height="' + _Utils$.escape_html(h) + '" style="border: 1px dotted #ECECEC;"></canvas>');
                                    const im = new Image();
                                    im.width = w;
                                    im.height = h;
                                    im.onload = () => {
                                        const cnv = $('#uxm-img-uploader-result-cnvs')[0];
                                        if (!cnv) {
                                            $upldr.val('');
                                            _p$.error(_N$, _m$, 'ERR:', 'Failed to Get Resizable Container');
                                            return;
                                        } //end if
                                        const ctx = cnv.getContext('2d');
                                        if (!ctx) {
                                            $upldr.val('');
                                            _p$.error(_N$, _m$, 'ERR:', 'Failed to Get Resizable Container Context');
                                            return;
                                        } //end if
                                        //ctx.fillStyle = '#FFFFFF'; // make sense just for image/jpeg
                                        //ctx.fillRect(0, 0, w, h); // make sense just for image/jpeg
                                        try {
                                            ctx.drawImage(im, 0, 0, w, h);
                                        } catch (imgerr) {
                                            $upldr.val('');
                                            _p$.error(_N$, _m$, 'ERR:', 'Failed to Draw Canvas Image:', imgerr);
                                            return;
                                        } //end try catch
                                        const imgResizedB64 = cnv.toDataURL(String(the_type_of_file), imgQuality); // preserve file type ; set image quality ... just for jpeg right now ...
                                        //_p$.log(_N$, _m$, 'Before fxDone' + the_frd.result.length, 'After: ' + imgResizedB64.length);
                                        if (String(imgResizedB64).length <= (1024 * 1024 * imgMaxResizeMBSize)) {
                                            isOK = true;
                                            if (typeof(fxDone) == 'function') {
                                                try {
                                                    fxDone(String(imgResizedB64), w, h, false, String(the_type_of_file), String(imgResizedB64).length, String(the_name_of_file));
                                                } catch (e) {
                                                    $upldr.val('');
                                                    _p$.error(_N$, _m$, 'ERR:', 'IMG CallBack Failed:', e);
                                                    return;
                                                } //end try catch
                                            } //end if
                                            if (isOK) {
                                                $imgpw.empty().html('');
                                                if (!clearPreview) {
                                                    $imgpw.html(String(imgRemoveBtn) + "\n" + '<img id="uxm-img-uploader-result-img" src="' + _Utils$.escape_html(imgResizedB64) + '" style="max-width:' + _Utils$.escape_html(widthPreview) + 'px; max-height:' + _Utils$.escape_html(heightPreview) + 'px; width:auto !important; height:auto !important;">');
                                                } //end if
                                            } //end if
                                        } else {
                                            $upldr.val('');
                                            $imgpw.text(txtWarn + 'Image Size after resize is higher than allowed size: ' + String(imgResizedB64).length + ' Bytes');
                                            return;
                                        } //end if else
                                    };
                                    im.src = String(the_frd.result);
                                } //end if else
                            },
                            500 // timeout
                        );
                    }; //end =>
                    //--
                    try {
                        the_frd.readAsDataURL(the_file);
                    } catch (fail) {
                        $upldr.val('');
                        _p$.error(_N$, _m$, 'ERR: Failed to Read Data URL:', fail);
                        return;
                    } //end try catch
                    //--
                } catch (err) {
                    //--
                    $upldr.val('');
                    _p$.error(_N$, _m$, 'ERR:', err);
                    return;
                    //--
                } //end try catch
                //--
            });
            //--
        }; //END
        _C$.VirtualImageUploadHandler = VirtualImageUploadHandler; // export


        // ========== PRIVATES


        /*
         * Allow or dissalow the use of ModalBox.
         * This will behave depending how is set the smartJ$Browser.param_ModalBoxActive
         *
         * @private
         *
         * @memberof smartJ$Browser
         * @method allowModalCascading
         * @static
         *
         * @return 	{Boolean} 		Will return FALSE if modal cascading is enabled for every situation when a ModalBox will open another ModalBox thus will force PopUp and will return TRUE for the rest of situations
         */
        const allowModalBox = function() {
            //--
            let isActive = false;
            if ((_C$.param_ModalBoxActive == 1) && (!_Te$tBrowser.checkIsMobileDevice())) {
                isActive = !!_C$.param_ModalBoxActive;
            } else if (_C$.param_ModalBoxActive > 1) {
                isActive = !!_C$.param_ModalBoxActive;
            } //end if else
            //--
            return !!isActive; // bool
            //--
        }; //END


        /*
         * Control the ModalBox Cascading in browser.
         * This will behave depending how is set the smartJ$Browser.param_ModalBoxNoCascade
         *
         * @private
         *
         * @memberof smartJ$Browser
         * @method allowModalCascading
         * @static
         *
         * @return 	{Boolean} 		Will return FALSE if modal cascading is enabled for every situation when a ModalBox will open another ModalBox thus will force PopUp and will return TRUE for the rest of situations
         */
        const allowModalCascading = function() {
            //--
            const _m$ = 'allowModalCascading';
            //--
            let cascadeModal = true;
            if (!!_C$.param_ModalBoxNoCascade) {
                cascadeModal = false;
            } //end if
            //--
            try {
                //--
                if (typeof(smartJ$ModalBox) != 'undefined') {
                    if (self.name) {
                        if (self.name == smartJ$ModalBox.getName()) {
                            return cascadeModal; // force popup
                        } //end if else
                    } //end if
                } //end if
                //--
            } catch (err) {
                _p$.error(_N$, _m$, 'ERR: Failed for Self:', err);
            } //end try catch
            //--
            try {
                //--
                if (typeof(parent.smartJ$ModalBox) != 'undefined') {
                    if (parent.name) {
                        if (parent.name == parent.smartJ$ModalBox.getName()) {
                            return cascadeModal; // force popup
                        } //end if else
                    } //end if
                } //end if
                //--
            } catch (err) {
                _p$.error(_N$, _m$, 'ERR: Failed for Parent:', err);
            } //end try catch
            //--
            return true;
            //--
        }; //END
        // no export


        /*
         * Return the a valid overlay ID based on original ID
         * If the overlay_id is empty will return the sfOverlayID as a fallback
         *
         * @private
         *
         * @memberof smartJ$Browser
         * @method OverlayValidID
         * @static
         * @arrow
         *
         * @return 	{String} 									A valid Overlay ID
         */
        const OverlayValidID = (overlay_id) => {
            //--
            overlay_id = _Utils$.stringPureVal(overlay_id, true); // cast to string, trim
            overlay_id = _Utils$.create_htmid(overlay_id);
            if (overlay_id == '') {
                overlay_id = String(sfOverlayID);
            } //end if
            //--
            return String(overlay_id);
            //--
        }; //END


        /*
         * Return the available growl type based on param_NotificationDialogType and detect if loaded
         * If param_NotificationDialogType is set to auto mode the jQuery.gritter will have priority against the jQuery.toastr if both are loaded
         *
         * @private
         *
         * @memberof smartJ$Browser
         * @method DialogSelectType
         * @static
         * @arrow
         *
         * @return 	{String} 									The selected growl type by settings logic or empty string
         */
        const DialogSelectType = () => { // ES6
            //--
            switch (_C$.param_NotificationDialogType) {
                case 'ui':
                    if (typeof(smartJ$UI) != 'undefined') {
                        return 'ui';
                    } //end if
                    break;
                case 'dialog':
                    if (typeof(SmartSimpleDialog) != 'undefined') {
                        return 'dialog';
                    } //end if
                    break;
                case 'alertable':
                    if ($.alertable) {
                        return 'alertable';
                    } //end if
                    break;
                case 'auto':
                    if (typeof(smartJ$UI) != 'undefined') {
                        return 'ui';
                    } else if (typeof(SmartSimpleDialog) != 'undefined') {
                        return 'dialog';
                    } else if ($.alertable) {
                        return 'alertable';
                    } //end if
                    break;
            } //end switch
            //--
            return 'native';
            //--
        }; //END
        // no export


        /*
         * Return the available growl type based on param_NotificationGrowlType and detect if loaded
         * If param_NotificationGrowlType is set to auto mode the jQuery.gritter will have priority against the jQuery.toastr if both are loaded
         *
         * @private
         *
         * @memberof smartJ$Browser
         * @method GrowlSelectType
         * @static
         * @arrow
         *
         * @return 	{String} 									The selected growl type by settings logic or empty string
         */
        const GrowlSelectType = () => { // ES6
            //--
            if (_C$.param_Notifications !== 'growl') {
                return '';
            } //end if
            //--
            switch (_C$.param_NotificationGrowlType) {
                case 'gritter':
                    if (typeof($.gritter) != 'undefined') {
                        return 'gritter';
                    } //end if
                    break;
                case 'toastr':
                    if (typeof($.toastr) != 'undefined') {
                        return 'toastr';
                    } //end if
                    break;
                case 'auto':
                    if (typeof($.gritter) != 'undefined') {
                        return 'gritter';
                    } else if (typeof($.toastr) != 'undefined') {
                        return 'toastr';
                    } //end if else
                    break;
            } //end switch
            //--
            return '';
            //--
        }; //END
        // no export


        /*
         * Return a valid type for the the jQuery Ajax
         *
         * @private
         *
         * @memberof smartJ$Browser
         * @method getValidjQueryAjaxType
         * @static
         * @arrow
         *
         * @return 	{String} 									The type ; if not valid will fallback to 'text'
         */
        const getValidjQueryAjaxType = (y_data_type) => {
            //--
            y_data_type = _Utils$.stringPureVal(y_data_type, true); // cast to string, trim
            switch (y_data_type) {
                case 'json': // Evaluates the response as JSON and returns a JavaScript object. The JSON data is parsed in a strict manner; any malformed JSON is rejected and a parse error is thrown
                case 'jsonp': // Loads in a JSON block using JSONP. Adds an extra "?callback=?" to the end of your URL to specify the callback
                case 'script': // Evaluates the response as JavaScript and returns it as plain text
                case 'html': // Expects valid HTML ; included javascripts are evaluated when inserted in the DOM
                case 'xml': // Expects valid XML
                case 'text': // Expects Text or HTML ; If HTML, includded javascripts are not evaluated when inserted in the DOM
                    break;
                default:
                    y_data_type = 'text';
            } //end switch
            //--
            return String(y_data_type);
            //--
        }; //END
        // no export


        /*
         * Inits and Open a Modal or PopUp Window by Form or Link
         *
         * @private
         *
         * @memberof smartJ$Browser
         * @method initModalOrPopUp
         * @static
         *
         * @param 	{String} 	strUrl 			The URL to open
         * @param 	{String} 	strTarget 		The URL target (window name)
         * @param 	{String} 	windowWidth 	*Optional* The Window Width
         * @param 	{String} 	windowHeight 	*Optional* The Window Height
         * @param 	{Enum} 		forcePopUp 		*Optional* Open Mode:
         * 		 0 (default) don't force, if modal Open Modal otherwise open PopUp
         * 		 1 force PopUp
         * 		-1 force Modal
         * @param 	{Enum} 		forceDims 		*Optional* If Modal must be set to 1 to force use the specified Width and Height
         * @param 	{Enum}		handlerMode		*Optional* If explicit set to 1 or 2 will use only a simple modal/popup without binding to the parent window ; if set to 2, the popup will not use redirect handler in addition to no binding ; if set to -1 the popup will not use redirect handler, but will use binding
         */
        const initModalOrPopUp = function(strUrl, strTarget, windowWidth, windowHeight, forcePopUp, forceDims, handlerMode) {
            //--
            const _m$ = 'initModalOrPopUp';
            //--
            strUrl = _Utils$.stringPureVal(strUrl); // cast to string, trim
            strTarget = _Utils$.stringPureVal(strTarget); // cast to string, trim
            windowWidth = _Utils$.format_number_int(parseInt(windowWidth), false);
            windowHeight = _Utils$.format_number_int(parseInt(windowHeight), false);
            //--
            forcePopUp = _Utils$.format_number_int(parseInt(forcePopUp), true);
            forceDims = _Utils$.format_number_int(parseInt(forceDims), true);
            handlerMode = _Utils$.format_number_int(parseInt(handlerMode), true);
            //--
            if ((((typeof(smartJ$ModalBox) != 'undefined') && (allowModalCascading()) && (forcePopUp != 1)) || (forcePopUp == -1)) && (allowModalBox())) { // use smart modal box
                //-- trasfer current parent refresh settings to the modal {{{SYNC-TRANSFER-MODAL-POPUP-REFRESH}}}
                if (handlerMode <= 0) {
                    smartJ$ModalBox.setRefreshParent(_C$.getFlag('RefreshState'), _C$.getFlag('RefreshURL'));
                } //end if
                //-- reset refresh on each modal open else a popup opened previous may refresh the parent on close
                _C$.setFlag('RefreshState', 0);
                _C$.setFlag('RefreshURL', '');
                //-- open
                if (forceDims != 1) {
                    smartJ$ModalBox.LoadURL(strUrl, _C$.param_ModalBoxProtected); // we do not use here custom size
                } else {
                    smartJ$ModalBox.LoadURL(strUrl, _C$.param_ModalBoxProtected, windowWidth, windowHeight); // we use here custom size
                } //end if else
                //--
            } else { // use pop up
                //--
                if ((strTarget == '') || (strTarget.toLowerCase() == '_blank') || (strTarget.toLowerCase() == '_self')) {
                    strTarget = String(defSmartPopupTarget); // dissalow empty target name
                } //end if
                //--
                let the_screen_width = 0;
                try { // try to center
                    the_screen_width = _Utils$.format_number_int(parseInt(screen.width));
                } catch (e) {} //end try catch
                if (the_screen_width <= 0) {
                    the_screen_width = 920;
                } //end if
                //--
                let the_screen_height = 0;
                try { // try to center
                    the_screen_height = _Utils$.format_number_int(parseInt(screen.height));
                } catch (e) {} //end try catch
                if (the_screen_height <= 0) {
                    the_screen_height = 700;
                } //end if
                //--
                let maxW = _Utils$.format_number_int(Math.round(the_screen_width * 0.90));
                if (windowWidth > maxW) {
                    windowWidth = maxW;
                } //end if
                //--
                let maxH = _Utils$.format_number_int(Math.round(the_screen_height * 0.80)); // on height there are menus or others
                if (windowHeight > maxH) {
                    windowHeight = maxH;
                } //end if
                //--
                if ((windowWidth < 200) || (windowHeight < 100)) {
                    windowWidth = maxW;
                    windowHeight = maxH;
                } //end if
                //--
                let windowTop = 50;
                let windowLeft = _Utils$.format_number_int(Math.round((the_screen_width / 2) - (windowWidth / 2)));
                if (windowLeft < 10) {
                    windowLeft = 10;
                } //end if
                //--
                if (objRefWinPopup) {
                    windowFocus(objRefWinPopup); // pre-focus if opened, with try/catch in windowFocus
                } //end if
                let useMonitor = true;
                let useRdr = true;
                if (handlerMode == -1) {
                    useRdr = false;
                } else if (handlerMode == 1) {
                    useMonitor = false;
                } else if (handlerMode == 2) {
                    useMonitor = false;
                    useRdr = false;
                } //end if
                let pUrl = String(_C$.param_LoaderBlank);
                if (useRdr !== true) {
                    pUrl = strUrl;
                } //end if
                try {
                    objRefWinPopup = window.open(pUrl, strTarget, 'top=' + windowTop + ',left=' + windowLeft + ',width=' + windowWidth + ',height=' + windowHeight + ',toolbar=0,scrollbars=1,resizable=1'); // most of modern browsers do no more support display toolbar on popUp
                } catch (err) {
                    _p$.error(_N$, _m$, 'ERROR raising a new PopUp Window:', err);
                } //end try catch
                if (objRefWinPopup) {
                    windowFocus(objRefWinPopup); // post-focus, with try/catch in windowFocus
                    if (useRdr === true) {
                        try { // redirect to the URL after loading
                            setTimeout(() => {
                                objRefWinPopup.location = strUrl;
                            }, _C$.param_TimeDelayCloseWnd);
                        } catch (err) {
                            _p$.error(_N$, _m$, 'ERROR redirecting the PopUp Window to [' + strUrl + ']:', err);
                        } //end try catch
                    } //end if
                    if (useMonitor !== true) {
                        _C$.setFlag('RefreshState', 0);
                        _C$.setFlag('RefreshURL', '');
                    } else {
                        try { // monitor when popup is closed, every 250ms
                            const wnd_popup_timer = setInterval(() => {
                                if (getFlag('PageAway') !== true) {
                                    let pop = _C$.getRefPopup();
                                    if (pop && pop.closed) {
                                        clearInterval(wnd_popup_timer); // first stop
                                        try { // {{{SYNC-POPUP-Refresh-Parent-By-EXEC}}}
                                            if (_C$.getFlag('RefreshState')) {
                                                if (!_C$.getFlag('RefreshURL')) {
                                                    self.location = self.location; // FIX: avoid location reload to resend POST vars !!
                                                } else {
                                                    self.location = String(_C$.getFlag('RefreshURL'));
                                                } //end if else
                                                _C$.setFlag('RefreshState', 0);
                                                _C$.setFlag('RefreshURL', '');
                                            } //end if
                                        } catch (err) {
                                            _p$.warn(_N$, _m$, 'WARN: Failed to Set Refresh on Self:', err);
                                        } //end try catch
                                        return false;
                                    } //end if
                                } //end if
                            }, 250);
                        } catch (err) {}
                    } //end if
                } //end if
                //--
            } //end if else
            //--
        }; //END
        // no export


    }
}; //END CLASS

smartJ$Browser.secureClass(); // implements class security

window.smartJ$Browser = smartJ$Browser; // global export

//==================================================================
//==================================================================

// #END

// ===== ifmodalbox_scanner.js

// [LIB - Smart.Framework / JS / Smart Modal Scanner]
// (c) 2006-2022 unix-world.org - all rights reserved
// r.8.7 / smart.framework.v.8.7

// DEPENDS: jQuery, smartJ$ModalBox, smartJ$Utils, smartJ$Browser

//==================================================================
//==================================================================

//================== [ES6]

/**
 * jQuery Plugin class :: Smart ModalBoxScanner (ES6)
 *
 * @package Sf.Javascript:Browser
 *
 * @requires		jQuery
 * @requires		smartJ$ModalBox
 * @requires		smartJ$Utils
 * @requires		smartJ$Browser
 *
 * @private : used by smartJ$ModalBox only
 *
 * @desc on document.ready will use jQuery to scan all a[data-smart] links from a page to implement Modal iFrame / iPopUp based on smartJ$Browser
 * @author unix-world.org
 * @license BSD
 * @file ifmodalbox_scanner.js
 * @version 20220730
 * @class jQuery.Plugin::smartJ$ModalBox@Scanner
 * @static
 *
 */
(() => {

    const _p$ = console;

    const $ = jQuery; // jQuery referencing

    const _Utils$ = smartJ$Utils;
    const _BwUtils$ = smartJ$Browser;
    const _ModalBox$ = smartJ$ModalBox;

    $(() => { // ON DOCUMENT READY
        //--
        const version = _ModalBox$.getVersion();
        //--
        //$('body').delegate('a[data-smart]', 'click', function(el) { // delegate() does the job also with new dom inserted links
        $('body').on('click', 'a[data-smart]', (el) => { // jQuery 3+ : it is equivalent with delegate() which was deprecated
            //--
            const $el = $(el.currentTarget);
            //--
            const dataSmart = $el.attr('data-smart');
            if (!dataSmart) {
                return true; // let click function as default
            } //end if
            //--
            const isModal = RegExp(/^open.modal/i).test(dataSmart);
            const isPopup = RegExp(/^open.popup/i).test(dataSmart);
            if ((isModal !== true) && (isPopup !== true)) { // does not have proper syntax
                return true; // let click function as default
            } //end if
            //--
            const attrHref = _Utils$.stringPureVal($el.attr('href'), true); // cast to string, trim
            if (attrHref == '') {
                _p$.error('iFrmBox Scanner (' + version + ')', 'The Clicked Data-Smart [' + dataSmart + '] Link has no Href Attribute: `' + _Utils$.stringTrim($el.text()) + '`');
                return false;
            } //end if
            //--
            let attrTarget = _Utils$.stringPureVal($el.attr('target'), true); // cast to string, trim
            if (attrTarget == '') {
                attrTarget = '_blank';
            } //end if
            //--
            let winWidth = _Utils$.format_number_int(parseInt($(window).width()), false);
            if (winWidth < 200) {
                winWidth = 200;
            } //end if
            let winHeight = parseInt($(window).height());
            if (winHeight < 100) {
                winHeight = 100;
            } //end if
            //--
            const aDim = dataSmart.match(/[0-9]+(\.[0-9][0-9]?)?/g); // dataSmart.match(/[0-9]+/g);
            let w = winWidth; // (aDim && (aDim[0] > 0)) ? aDim[0] : winWidth;
            let h = winHeight; // (aDim && (aDim[1] > 0)) ? aDim[1] : winHeight;
            let u = (aDim && (aDim[2] > 0)) ? aDim[2] : 0;
            //--
            if (aDim) {
                if (aDim[0] > 0) {
                    if (aDim[0] < 1) {
                        w = aDim[0] * winWidth;
                    } else {
                        w = aDim[0];
                    } //end if else
                } //end if
                if (aDim[1] > 0) {
                    if (aDim[1] < 1) {
                        h = aDim[1] * winHeight;
                    } else {
                        h = aDim[1];
                    } //end if else
                } //end if
            } //end if
            //--
            w = _Utils$.format_number_int(parseInt(w), false);
            h = _Utils$.format_number_int(parseInt(h), false);
            u = _Utils$.format_number_int(parseInt(u), false);
            //--
            if (w > winWidth) {
                w = _Utils$.format_number_int(parseInt(winWidth * 0.9), false);
            } //end if
            if (w < 200) {
                w = 200;
            } //end if
            if (h > winHeight) {
                h = _Utils$.format_number_int(parseInt(winHeight * 0.9), false);
            } //end if
            if (h < 100) {
                h = 100;
            } //end if
            //--
            let mode = 0; // 1 = popup, 0 = modal if not in modal, -1 = modal
            switch (u) {
                case 1:
                    mode = -1; // force modal
                    break;
                default:
                    mode = 0; // default
            } //end switch
            //--
            if (isModal === true) {
                _BwUtils$.PopUpLink(attrHref, attrTarget, w, h, mode, 1);
            } else if (isPopup === true) {
                _BwUtils$.PopUpLink(attrHref, attrTarget, w, h, 1, 1);
            } //end if else
            //--
            return false;
            //--
        });
        //--
    }); //END ON DOCUMENT READY FUNCTION

})();

//==================================================================
//==================================================================

// #END

// ===== [#]

// # JS Package: smart-framework.pak.js :: #END#
