
if (typeof naviki == "undefined") {
	var naviki = {}
}
if (typeof naviki.util == "undefined") {
	naviki.util = {}
}

/**
 * depends on
 * - naviki.util.geo
 * - naviki.util.second
 * - naviki.util.user
 */
naviki.util.wayduration = {

	/**
	 * This method adds the travel duration to the given element.
	 * @param durationInSeconds in seconds
	 * @param pathInfoTravelTimeElement the view element
	 */
	addDuration: function (durationInSeconds, pathInfoTravelTimeElement) {
		if (durationInSeconds > 0) {
			const time = naviki.util.second.toTime(durationInSeconds) + " " + naviki.Lang.getLL('base.hour');
			pathInfoTravelTimeElement.html(time);
			pathInfoTravelTimeElement.show();
		} else {
			pathInfoTravelTimeElement.hide();
		}
	},

	/**
	 * This method returns the duration of a path in seconds and takes the default speed
	 * of the selected profile and the user's average speed into consideration.
	 * @param duration The path duration in seconds
	 * @param routingProfileName
	 */
	getNormalizedDuration: function(duration, routingProfileName, routingServer) {
		var defaultSpeed = 20.0;
		var denominator;
		if (naviki.util.user.isLoggedIn()) {
			var userProfileSpeed = naviki.util.user.getUserAverageSpeed();
			denominator = (userProfileSpeed / defaultSpeed) > 0.0 ? (userProfileSpeed / defaultSpeed) : 1.0;
		} else {
			var server = this.getRoutingServerByName(routingProfileName, routingServer);
			var routingServerSpeed = server != null && server.defaultSpeed > 0.0 ? server.defaultSpeed : defaultSpeed;
			denominator = (routingServerSpeed / defaultSpeed) > 0.0 ? (routingServerSpeed / defaultSpeed) : 1.0;
		}
		return Math.round(duration * (1.0 / denominator));
	},

	calcDurationInSeconds: function (path, recordedPathDuration, routingServer) {
		return this.calcDurationInSecondsImpl(path.routeInstructions, path.routingProfile, path.km, recordedPathDuration, routingServer);
	},

	/**
	 * calc duration of way in seconds, use instructions or given recorded path duration
	 *
	 * @param routeInstructions instructions array
	 * @param routingProfile profile name as string
	 * @param pathLength in km
	 * @param recordedPathDuration in seconds
	 * @param routingServer list of available routing server
	 */
	calcDurationInSecondsImpl: function (routeInstructions, routingProfile, pathLength, recordedPathDuration, routingServer) {
		if (routeInstructions != null && routeInstructions.length > 5) {
			var durationInSeconds = 0;
			var instr = JSON.parse(routeInstructions);
			for (var i = 0; i < instr.length; i++) {
				durationInSeconds += instr[i][4];
			}
			durationInSeconds = this.getNormalizedDuration(durationInSeconds, routingProfile, routingServer);
			return durationInSeconds;
		} else if (recordedPathDuration != null && recordedPathDuration > 0) {
			return recordedPathDuration;
		} else if ((recordedPathDuration == null || recordedPathDuration <= 0) && pathLength > 0) {
			return Math.round((pathLength / naviki.util.user.getUserAverageSpeed()) * 3600.0);
		} else {
			return 0;
		}
	},

	/**
	 * Returns a server by given name.
	 * e.g. {uid: 41, routingUrl: "r.naviki.org/5030", profileName: "daily", frontendName: "Alltag", useForRoundTrip: 1}
	 *
	 * @returns {Object}
	 */
	getRoutingServerByName: function(name, routingServer) {
		if (routingServer != null) {
			for (var index = 0; index < routingServer.length; index++) {
				if (routingServer[index].profileName === name) {
					return routingServer[index];
				}
			}
		}
		return null;
	}
}
if (typeof naviki == "undefined") {
	var naviki = {}
}
if (typeof naviki.util == "undefined") {
	naviki.util = {}
}

naviki.util.way = {

	isCalculated: function(path) {
		return path.wayType == naviki.CONSTANTS.WAY_TYPE.wayTypeRoutingRequest
			|| path.wayType == naviki.CONSTANTS.WAY_TYPE.wayTypeRoundtrip
			|| path.wayType == naviki.CONSTANTS.WAY_TYPE.wayTypeMatchedPath;
	},

	isOwnRecordOrUploadedAsRecorded: function(path) {
		return this.isCurrentUserWayOwner(path)
			&& path.waySource == naviki.CONSTANTS.WAY_SOURCE.waySourceRealPath
			&& (path.wayType == naviki.CONSTANTS.WAY_TYPE.wayTypeRecordedPath
				|| path.wayType == naviki.CONSTANTS.WAY_TYPE.wayTypeUploadedPath);
	},

	isCurrentUserWayOwner: function(path) {
		return path.feUserid && naviki.util.user.getUserId() && path.feUserid == naviki.util.user.getUserId();
	},

	isOsmRelation: function(path) {
		return path.wayType == naviki.CONSTANTS.WAY_TYPE.wayTypeOsmRelation;
	},

	getViaPoints: function(path) {
		return path.viaPoints;
	},

	createWayTypeIconElement: function(path, calculatedIconEnabled) {
		const iconElement = $('<span/>', {});
		this.updateWayTypeIconElement(path, iconElement, calculatedIconEnabled);
		return iconElement;
	},

	updateWayTypeIconElement: function(path, iconElement, calculatedIconEnabled) {
		iconElement.removeClass(); // remove all classes
		iconElement.attr('title', '');

		if (path.premiumWay) {
			iconElement.addClass("icon-routes-premium");
			iconElement.attr('title', naviki.Lang.getLL("tx_naviki_pimapcontrol.premium.tip"));
		} else if (path.osmRelationsId > 0) {
			iconElement.addClass("icon-routes-confirmed");
			iconElement.attr('title', naviki.Lang.getLL("tx_naviki_pimapcontrol.official.tip"));
		} else {
			if (this.isCalculated(path) && calculatedIconEnabled) {
				iconElement.addClass("icon-routes-calculated");
				iconElement.attr('title', naviki.Lang.getLL("tx_naviki_pimapcontrol.calculated.tip"));
			} else if (this.isOwnRecordOrUploadedAsRecorded(path)) {
				iconElement.addClass("icon-routes-cycled");
				iconElement.attr('title', naviki.Lang.getLL("tx_naviki_pimapcontrol.recorded.tip"));
			} else {
				// do not show any icon (empty span element)
			}
		}
	},

	isWayEditable: function(path) {
		// show edit button (or similar) when all following conditions are fulfilled:
		// - a way is loaded
		// - user is logged in and owner of the way
		// - way is NOT an official or premium route (OR it is official or premium and user has the permium way permission)
		const isLoggedIn = naviki.util.user.isLoggedIn();
		const currentUserId = naviki.util.user.getUserId()
		const canEditPremiumWays = naviki.util.user.hasUserPremiumWayPermission();

		return path != null && path.uid > 0
			&& isLoggedIn && path.feUserid == currentUserId
			&& ((path.osmRelationsId < 1 && !path.premiumWay) || canEditPremiumWays);
	},

	completeDestinations(destinations) {
		// update ui after running a bunch of name service requests (NAV-6531)
		var inWorkCount = 0;
		var anyUpdateTitleOnAddressComplete = false;
		var anyGeoFieldComplete = false;
		const callback = function(geoFieldComplete, updateTitleOnAddressComplete) {
			if (updateTitleOnAddressComplete) anyUpdateTitleOnAddressComplete = true;
			if (geoFieldComplete) anyGeoFieldComplete = true;

			inWorkCount--;

			if (inWorkCount % 20 == 0) {
				$(document).trigger("destinationAddressFieldReady", {force: false});
				
				if (inWorkCount == 0 && anyGeoFieldComplete) {
					$(document).trigger("destinationGeoFieldReady");
				}
			}
		}

		for (var index = 0; index < destinations.length; index++) {
			const dst = destinations[index];
			if (dst.isGeoFieldEmpty() || dst.isAddressFieldEmpty()) {
				inWorkCount++;
				dst.complete(callback);
			}
		}
	}
}
if (typeof naviki == "undefined") {
	var naviki = {}
}

if (typeof naviki.util == "undefined") {
	naviki.util = {}
}

naviki.util.api = {

	PrivateWayState: {
		publicWay: 0,
		privateWay: 1,
		sharedWay: 2
	},

	isTokenRefresh: false,
	ajaxRequestStack: new Array(),

	getAjaxDispatcherBasePath: function() {
		return "/" + naviki.CONSTANTS.LANGUAGE + "/naviki/ajax-dispatcher/";
	},
	
	setAccessToken: function(accessToken) {
		if(typeof(localStorage) !== "undefined") {	
			window.localStorage.setItem("_n_a_at", accessToken);
		}
	},
		
	getAccessToken: function() {
		if(typeof(localStorage) !== "undefined") {	
			var accessToken = window.localStorage.getItem("_n_a_at");
			if(accessToken != "undefined" && accessToken != null) {
				return accessToken;
			}
		}
		return null;
	},
		
	removeAccessToken: function() {
		if(typeof(localStorage) !== "undefined") {	
			window.localStorage.removeItem("_n_a_at");
		}
	},
		
	setRefreshToken: function(refreshToken) {
		if(typeof(localStorage) !== "undefined") {	
			window.localStorage.setItem("_n_a_rt", refreshToken);
		}
	},
		
	getRefreshToken: function() {
		if(typeof(localStorage) !== "undefined") {	
			var refreshToken = window.localStorage.getItem("_n_a_rt");
			if(refreshToken != "undefined" && refreshToken != null) {
				return refreshToken;
			}
		}
		return null;
	},
		
	removeRefreshToken: function() {
		if(typeof(localStorage) !== "undefined") {	
			window.localStorage.removeItem("_n_a_rt");
		}
	},
	
	request: function(options) {
		var parentBeforeSend = null;
		if (options.beforeSend != undefined && typeof options.beforeSend === "function") {
			parentBeforeSend = options.beforeSend;
		}
		options.beforeSend = function(xhr) {
			if (parentBeforeSend != null && typeof parentBeforeSend === "function") {
	        	parentBeforeSend();
	        }
			if (naviki.util.api.getAccessToken() != null) {
				xhr.setRequestHeader('Authorization', 'Bearer ' + naviki.util.api.getAccessToken());
			}
			if (typeof(naviki.CONSTANTS.LOCALE_ALL) !== "undefined") {
				const locale = naviki.CONSTANTS.LOCALE_ALL.replace("_","-");
				xhr.setRequestHeader('Accept-Language', locale);
			}
	    }
		
		var parentError = null;
		if (options.error != undefined && typeof options.error === "function") {
			parentError = options.error;
		}
		options.error = function(jqXHR, textStatus, errorThrown) {
			if (jqXHR != null && jqXHR.status != null && jqXHR.status == 401) {
				naviki.util.api.ajaxRequestStack.push(options);
				if (!naviki.util.api.isTokenRefresh) {
					naviki.util.api.isTokenRefresh = true;
					naviki.util.api.refreshToken();
				}
				return;
			}
			if (parentError != null && typeof parentError === "function") {
				parentError(jqXHR);
	        }
		}
		
		return $.ajax(options);
	},
	
	refreshToken: function() {
		var params = "?request[format]=json&request[controller]=FeUser&request[action]=refreshToken";
		params += "&request[arguments][refreshToken]=" + naviki.util.api.getRefreshToken();

		$.ajax({
			url: naviki.util.api.getAjaxDispatcherBasePath() + encodeURI(params),
			type: "GET",
			cache: false,
			dataType : "json",
			contentType: 'application/json; charset=utf-8',
		    processData: false,
			success : function(data) {					
				if (data.status && data.accessToken && data.refreshToken) {
					naviki.util.api.setAccessToken(data.accessToken);
					naviki.util.api.setRefreshToken(data.refreshToken);
					naviki.util.user.setUserId(data.feUserId); // could be missing, if last login long ago
					while(naviki.util.api.ajaxRequestStack.length > 0) {
						$.ajax(naviki.util.api.ajaxRequestStack.pop());
					}
					naviki.util.api.ajaxRequestStack = new Array();
					naviki.util.api.isTokenRefresh = false;
				} else {
					naviki.util.api.logoutFeUser();
				}
			},
			error: function(err) {
				naviki.util.api.logoutFeUser();
			}
		});
	},
	
	logoutFeUser: function() {
		naviki.util.api.removeAccessToken();
		naviki.util.api.removeRefreshToken();

		naviki.util.user.setUserProfile(null);
		naviki.util.user.setUsername(null);
		naviki.util.user.setUserId(null);
		naviki.util.user.setActivated(false);
		naviki.util.user.setUserAverageSpeed(null);
		naviki.util.user.setUserEnergyUnit(null);
		naviki.util.user.setUserLengthUnit(null);
		naviki.util.user.setUserWayFilters(null);
		naviki.util.user.setUserWaySort(null);

		naviki.util.contest.clear();
	},

	checkUserLanguage: function() {
		const userProfile = naviki.util.user.getUserProfile();
		if (!userProfile) {
			return;
		}
		const pageLanguage = naviki.CONSTANTS.LANGUAGE.toUpperCase();
		if (!userProfile || userProfile.language.toUpperCase() !== pageLanguage) {
			this.updateUserLanguage(pageLanguage, function() {
				console.log("Updated user language.");
			});
		}
	},

	updateUserLanguage: function(language, callback) {
		const userDataObject = {
			uid: naviki.util.user.getUserId(),
			username: naviki.util.user.getUsername(),
			language: language
		};
		this.updateUserProfile(userDataObject, callback);
	},

	updateUserLengthUnit: function(lengthUnit) {
		const userDataObject = {
			uid: naviki.util.user.getUserId(),
			username: naviki.util.user.getUsername(),
			lengthUnit: lengthUnit
		};
		this.updateUserProfile(userDataObject, function() {});
	},

	updateUserProfile: function(userDataObject, callback) {
		if (!naviki.util.user.isLoggedIn()) {
			if (callback) {
				callback();
			}
			return;
		}
		const url = encodeURI(naviki.CONSTANTS.API_BASE_URL) + "User/2/update/";
		naviki.util.api.request({
			url: url,
			type: 'PUT',
			cache: false,
			contentType: 'application/json; charset=utf-8',
			data: JSON.stringify(userDataObject),
			error: function(errorData) {
				console.log(errorData);
			},
			complete: callback
		});
	},

	loadUserProfile: function() {
		if (!naviki.util.user.isLoggedIn()) {
			return;
		}
		const url = encodeURI(naviki.CONSTANTS.API_BASE_URL) + "User/2/find/?package=org.naviki";
		naviki.util.api.request({
			url: url,
			type: 'GET',
			cache: false,
			dataType: "json",
			contentType: 'application/json; charset=utf-8',
			success: function (data) {
				naviki.util.user.setUserProfile(data);
				naviki.util.user.setUserLengthUnit(data.lengthUnit);
				naviki.util.user.setUserEnergyUnit(data.energyUnit);
				console.log("Loaded user profile.");

				// check and force update of length unit, it may still be set "undefined" in database
				naviki.util.localeformatter.getCurrentUserLengthUnit();

				// check and update user language
				naviki.util.api.checkUserLanguage();
			},
			error: function (errorData) {
				console.log(errorData);
			}
		});
	},

	validateAndUpdateWayPrivacy(path, callback) {
		if (!naviki.util.user.isLoggedIn() || path.privateWay != this.PrivateWayState.privateWay || path.uid < 1) {
			if (callback) {
				callback();
			}
			return;
		}

		path.privateWay = this.PrivateWayState.sharedWay;

		const updateData = { uid: path.uid, privateWay: path.privateWay };
		this.updateWay(updateData, callback);
	},

	updateWay: function(wayDataObject, callback) {
		const url = encodeURI(naviki.CONSTANTS.API_BASE_URL) + "Way/update/" + wayDataObject.uid + "/";
		naviki.util.api.request({
			url: url,
			type: 'PUT',
			cache: false,
			contentType: 'application/json; charset=utf-8',
			data: JSON.stringify(wayDataObject),
			error: function(errorData) {
				console.log(errorData);
			},
			complete: callback
		});
	},

	getFindWayApiUrl: function(uuid, uid) {
		if (uuid == null && uid != null) {
			const apiV5BaseUrl = window.location.protocol + "//" + window.location.host + "/naviki/api/v5/";
			return encodeURI(apiV5BaseUrl + "Way/find/" + uid + "/");
		}

		const apiCall = naviki.util.user.isLoggedIn() ? "find/withUser/" : "find/anonymous/";
		return encodeURI(naviki.CONSTANTS.API_BASE_URL + "Way/2/" + apiCall + uuid + "/");
	}
}
if (typeof naviki == "undefined") {
	var naviki = {}
}

if (typeof naviki.util == "undefined") {
	naviki.util = {}
}

naviki.util.binary = {

	prepare : function(binary, length) {
		if (binary.length < length) {
			do {
				binary = "0" + binary;
			} while (binary.length < length);
		}
		return "" + binary;
	},

	getBit : function(binary, pos) {
		binary = naviki.util.prepareBinary("" + binary, pos + 1);
		tmp = binary.split("");
		return tmp[tmp.length - (pos + 1)];
	},

	setBit : function(binary, pos, value) {
		binary = naviki.util.prepareBinary("" + binary, pos + 1);
		tmp = binary.split("");
		tmp[tmp.length - (pos + 1)] = value;
		binary = "";
		for (var i = 0; i < tmp.length; i++) {
			binary += tmp[i];
		}
		return binary;
	}
}
if (typeof naviki == "undefined") {
	var naviki = {}
}
if (typeof naviki.util == "undefined") {
	naviki.util = {}
}

naviki.util.contest = {

	data: null,

	loadContest: function(contestId, callback) {
		if (this.data != null) {
			callback(this.data);
			return;
		}

		const _self = this;
		const url = encodeURI(naviki.CONSTANTS.API_BASE_URL + "Contest/findContest/" + contestId + "/");
		naviki.util.api.request({
			url : url,
			type : "GET",
			cache: false,
			dataType : "json",
			contentType: 'application/json; charset=utf-8',
			success : function(data) {
				if (!data || !data.contest) {
					return;
				}
				_self.data = data;
				callback(_self.data);
			}
		});
	},

	clear: function() {
		this.data = null;
	}
}
if(typeof naviki == "undefined"){
	var naviki = {}
}

if(typeof naviki.util == "undefined"){
	naviki.util = {}
}

naviki.util.compression = {
		
		// Encodes one-dimensional and two-dimensional arrays.
		//
		// @param array decodedObject
		//
		// e.g. decodedObject = array: ((1,2,3,4),(5,6,7,8),(9,10,11,12),(13,14,15,16))
		//		result = "ACEGGGGGGGGGGGGG"
		//
		//		decodedObject = array: (1,2,3,4)
		//		result = "AAAA"
		//
		encode: function(decodedObject)
		{
		    var encodedString = "";
		    var prevRow;
		    var row;
		    
		    var delta, prevVal = 0;
		    
		    for(var i = 0; i < decodedObject.length; i++)
		    {
		        if($.isArray(decodedObject[i]))
		        {
		            row = decodedObject[i];
		            
		            // If is the first row, than
		            // generate a previous row with 0 values​​.
		            if(i == 0)
		            {
		                prevRow = new Array();
		                for(var k = 0; k < row.length; k++)
		                {
		                    prevRow.push(0);
		                }
		            }
		            
		            for(var j = 0; j < row.length; j++)
		            {
		                // Calculate the delta from the previous value.
		                delta = parseInt(row[j]) - parseInt(prevRow[j]);
		                
		                // Encode the delta value.
		                encodedString = naviki.util.compression.encodeValue(encodedString,delta);
		            }
		            // Hold the current row.
		            prevRow = row;
		        }
		        else
		        {
		            // Calculate the delta from the previous value.
		            delta = parseInt(decodedObject[i]) - parseInt(prevVal);
		            // Hold the current value.
		            prevVal = parseInt(decodedObject[i]);
		            // Encode the delta value.
		            encodedString = naviki.util.compression.encodeValue(encodedString,delta);
		        }
		    }
		    
		    return encodedString;
		},

		encodeValue: function(encodedString,delta)
		{
		    // Left-shift the value one bit.
		    // If the original decimal value is negative, invert this encoding.
		    delta = (delta < 0) ? ~(delta << 1) : (delta << 1);

		    // OR each value with 0x20 if another 5-bit chunk follows.
		    // Add 63 to each value.
		    // Convert each value to its ASCII equivalent.
		    while (delta >= 0x20) {
		    	var val = (0x20 | (delta & 0x1f)) + 63;
		        encodedString += String.fromCharCode(val);
		        delta >>= 5;
		    }
		    encodedString += String.fromCharCode(delta + 63);
		    return encodedString;
		},
		
		// Decodes an encoded string.
		//
		// @param string encodedString The encoded string.
		// @param int columnCount Number of values ​​per row.
		//
		// e.g. encodedString = "ACEGGGGGGGGGGGGG"
		//		columnCount = 4
		//		result = array: ((1,2,3,4),(5,6,7,8),(9,10,11,12),(13,14,15,16))
		//
		//		encodedString = "AAAA"
		//		columnCount = 1
		//		result = array: (1,2,3,4)
		//
		decode: function(encodedString,columnCount)
		{
		    var length = encodedString.length;
		    var idx = 0;
		    
		    var byte;
		    var shift;
		    var delta;
		    var value;
		    var res;
		    
		    var result = new Array();
		    var prevRow;
		    var row;
		    
		    while (idx < length)
		    {
		        // If is the first row, than
		        // generate a previous row with 0 values​​.
		        if(result.length == 0)
		        {
		            prevRow = new Array();
		            for(var i = 0; i < columnCount; i++)
		            {
		                prevRow.push(0);
		            }
		        }
		  
		        row = new Array();
		        for(var i = 0; i < columnCount; i++)
		        {
		            byte  = 0;
		            res   = 0;
		            shift = 0;

		            do {
		                // The statement returns the ASCII code for the character at index.
		                // Subtract 63 to get the original value.
		                // (63 was added to ensure proper ASCII characters are displayed
		                // in the encoded polyline string, which is `human` readable)
		                byte = encodedString.charCodeAt(idx++) - 63;
		                // AND the bits of the byte with 0x1f to get the original 5-bit `chunk.
		                // Then left shift the bits by the required amount, which increases
		                // by 5 bits each time.
		                // OR the value into results, which sums up the individual 5-bit chunks
		                // into the original value.  Since the 5-bit chunks were reversed in
		                // order during encoding, reading them in this way ensures proper
		                // summation.
		                res |= (byte & 0x1F) << shift;
		                shift += 5;
		                // Continue while the read byte is >= 0x20 since the last `chunk`
		                // was not OR'd with 0x20 during the conversion process. (Signals the end)
		            } while (byte >= 0x20);
		     
		            // Check if negative, and convert. (All negative values have the last bit set)
		            delta = (res & 1) ? ~(res >> 1) : (res >> 1);
		            
		            // Summing the delta to the previous value.
		            value = parseInt(prevRow[i]);
		            value += delta;
		            
		            row.push(value);
		        }
		        result.push(row);
		        // Hold the current row.
		        prevRow = row;
		    }

		    // if the column count 1, than return a one-dimensional array.
		    if(columnCount == 1)
		    {
		        var change = new Array();
		        for(var i = 0; i < result.length; i++)
		        {
		            change.push(result[i][0]);
		        }
		        result = change;
		    }

		    return result;
		}
};
if (typeof naviki == "undefined") {
	var naviki = {}
}

if (typeof naviki.util == "undefined") {
	naviki.util = {}
}

naviki.util.cookie = {

	read : function(key) {
		var c_value = document.cookie;
		var c_start = c_value.indexOf(" " + key + "=");
		if (c_start == -1) {
			c_start = c_value.indexOf(key + "=");
		}
		if (c_start == -1) {
			c_value = null;
		} else {
			c_start = c_value.indexOf("=", c_start) + 1;
			var c_end = c_value.indexOf(";", c_start);
			if (c_end == -1) {
				c_end = c_value.length;
			}
			c_value = unescape(c_value.substring(c_start, c_end));
		}
		return c_value;
	},

	write : function(key, value) {
		var e = 1000 * 60 * 60 * 24 * 365;
		var a = new Date();
		a = new Date(a.getTime() + e);
		document.cookie = key + '=' + value + ';expires=' + a.toGMTString()
				+ ';path=/';
	},

	remove : function(key) {
		document.cookie = key + '=;expires=Thu, 01-Jan-70 00:00:01 GMT;path=/';
	}
}
if (typeof naviki == "undefined") {
	var naviki = {}
}

if (typeof naviki.util == "undefined") {
	naviki.util = {}
}

naviki.util.geo = {

	/**
	 * Calculates the distance between two points.
	 * 
	 * @param p1 Object that contains the geo-coordinates.
	 * @param p2 Object that contains the geo-coordinates.
	 * @return integer The distance in km.
	 */
	calculateDistance : function(p1, p2) {
		var radlat1 = Math.PI * p1.lat / 180;
		var radlat2 = Math.PI * p2.lat / 180;
		var radlon1 = Math.PI * p1.lng / 180;
		var radlon2 = Math.PI * p2.lng / 180;
		var theta = p1.lng - p2.lng;
		var radtheta = Math.PI * theta / 180;
		var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1)
				* Math.cos(radlat2) * Math.cos(radtheta);
		dist = Math.acos(dist);
		dist = dist * 180 / Math.PI;
		dist = dist * 60 * 1.1515;
		dist = dist * 1.609344;

		if (!isNaN(dist)) {
			return dist;
		}

		return 0;
	},

	/**
	 * Calculate the bearing between two positions as a value from 0-360
	 *
	 * @param lat1 - The latitude of the first position
	 * @param lng1 - The longitude of the first position
	 * @param lat2 - The latitude of the second position
	 * @param lng2 - The longitude of the second position
	 *
	 * @return int - The bearing between 0 and 360
	 */
	calculateBearing : function(lat1, lng1, lat2, lng2) {
		var dLon = (lng2 - lng1);
		var y = Math.sin(dLon) * Math.cos(lat2);
		var x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1)
				* Math.cos(lat2) * Math.cos(dLon);
		var brng = Math.atan2(y, x) * 180 / Math.PI;
		return 360 - ((brng + 360) % 360);
	},

	/**
	 * Parses a String in GMS-Format to Latitude / Longitude Format
	 *
	 * @param destination - Destination object containing the address
	 * @returns {undefined|{address: string, lng: number, lat: number}}
	 */
	parseGMS: function (destination) {
		// Check for Coordinates in GMS System
		const gmsRexExp = /\b(?<!\d°)(\d{1,2})°(\s*\d{1,2})?(['`´])?(\s*\d{1,2}[.,]?\d+?)?("|'{2}|`{2}|´{2})?([NS])\s*(\d{1,3})°\s*(\d{1,2})?(['`])?\s*(\d{1,2}[.,]?\d+?)?("|'{2}|`{2}|´{2})?([WE])\b/;
		const match = gmsRexExp.exec(destination.address);

		if (!match) {
			return undefined;
		}

		// First index is the whole string
		const degreesLat = parseFloat(match[1]);
		// check for Minutes on Latitute
		let minutesLat = 0.0;
		if (match[3] === "\'" || match[3] === "\´" || match[3] === "\`") {
			// If apostrophe is present second match is the number of it
			minutesLat = parseFloat(match[2]);
		}

		// check for Seconds on Latitude
		let secondsLat = 0.0;
		if (match[5] === "\"" || match[5]=== "\'\'" || match[5]=== "\`\`" || match[5]=== "\´\´") {
			secondsLat = parseFloat(match[4].replace(",", "."));
		}

		let latitudeSign = 1;
		if (match[6].toUpperCase() === "S") {
			latitudeSign = -1;
		}
		const degreesLon = parseFloat(match[7]);

		// check for Minutes on Longitude
		let minutesLon = 0.0;
		if (match[9] === "\'" || match[9] === "\´" || match[9] === "\`") {
			minutesLon = parseFloat(match[8]);
		}

		// check for Seconds on Longitude
		let secondsLon = 0.0
		if (match[11] ==="\"" || match[11]==="\'\'" || match[11]==="\`\`" || match[11]==="\´\´") {
			secondsLon = parseFloat(match[10].replace(",", "."));
		}

		let longitudeSign = 1;
		if (match[12].toUpperCase() === "W") {
			longitudeSign = -1;
		}

		const latitude = latitudeSign * (degreesLat + minutesLat / 60 + secondsLat / 3600)
		const longitude = longitudeSign * (degreesLon + minutesLon / 60 + secondsLon / 3600)

		const newLocationString = latitude + "," + longitude;

		return {
			lat: latitude,
			lng: longitude,
			address: newLocationString
		}
	}
}
if(typeof naviki == "undefined"){
	var naviki = {}
}

if(typeof naviki.util == "undefined"){
	naviki.util = {}
}

naviki.util.googlepolyline = {
		
		/**
		 * Decodes a polyline that was encoded using the Google Maps method.
		 *
		 * The encoding algorithm is detailed here:
		 * http://code.google.com/apis/maps/documentation/polylinealgorithm.html
		 *
		 * This function is based off of Mark McClure's JavaScript polyline decoder
		 * (http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/decode.js)
		 * which was in turn based off Google's own implementation.
		 *
		 * This function assumes a validly encoded polyline.  The behaviour of this
		 * function is not specified when an invalid expression is supplied.
		 *
		 * @param String encoded the encoded polyline.
		 * 
		 * @return Array an Nx2 array with the first element of each entry containing
		 *  the latitude and the second containing the longitude of the
		 *  corresponding point.
		 */
		decode : function(encoded, precision) {

			if (typeof precision == "undefined" || precision == null) {
				precision = 5;
			}
			
			var precisionMultiplier = Math.pow(10, -precision);
			var index = 0, lat = 0, lng = 0, array = [];
			var b, shift, result;
			var dlat,dlng;
			
			while (index < encoded.length) {
				// The encoded polyline consists of a latitude value followed by a
				// longitude value.  They should always come in pairs.  Read the
				// latitude value first.
				shift  = 0;
				result = 0;
				do {
					// The statement returns the ASCII code for the character at index.  
					// Subtract 63 to get the original value.
					// (63 was added to ensure proper ASCII characters are displayed
					// in the encoded polyline string, which is `human` readable)
					b = encoded.charCodeAt(index++) - 63;
					// AND the bits of the byte with 0x1f to get the original 5-bit `chunk.
					// Then left shift the bits by the required amount, which increases
					// by 5 bits each time.
					// OR the value into results, which sums up the individual 5-bit chunks
					// into the original value.  Since the 5-bit chunks were reversed in
					// order during encoding, reading them in this way ensures proper
					// summation.
					result |= (b & 0x1f) << shift;
					shift += 5;
					// Continue while the read byte is >= 0x20 since the last `chunk`
					// was not OR'd with 0x20 during the conversion process. (Signals the end)
				} while (b >= 0x20);
				// Check if negative, and convert. (All negative values have the last bit set)	
				dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
				// Compute actual latitude since value is offset from previous value.
				lat += dlat;
				
				// The next values will correspond to the longitude for this point.
				shift  = 0;
				result = 0;
				do {
					b = encoded.charCodeAt(index++) - 63;
					result |= (b & 0x1f) << shift;
					shift += 5;
				} while (b >= 0x20);
				dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
				lng += dlng;
				
				var latVal = (lat * precisionMultiplier).toFixed(precision);
				var lngVal = (lng * precisionMultiplier).toFixed(precision);
				array.push([latVal, lngVal]);
			}
			
			return array;
		},

		/**
		 * Encodes a polyline that was decoded using the Google Maps method.
		 * (https://developers.google.com/maps/documentation/utilities/polylinealgorithm?hl=de&csw=1)
		 */
		encode : function(points, precision){
			
			precision = Math.pow(10, precision);
			
			var dlat, dlng;
			var latE5,lngE5;
			var nextValue;
			var plat = 0, plng = 0;
			var encodedPoints = "";
			
			for(var i = 0; i < points.length; i++){	
				// Take the decimal value and multiply it by 1e5.
				latE5 =  parseInt(points[i][0] * precision);    
			    // Calculate the delta from the previous value.
			    dlat = latE5 - plat;	    
			    // Hold the current value.
			    plat = latE5;    
			    // Left-shift the value one bit.
			    dlat = dlat << 1;    
			    // If the original decimal value is negative, invert this encoding.
			    if (dlat < 0) {
			    	dlat = ~(dlat);
			    }    
			    // OR each value with 0x20 if another 5-bit chunk follows.
			    // Add 63 to each value.
			    // Convert each value to its ASCII equivalent.
				while (dlat >= 0x20) {
					nextValue = (0x20 | (dlat & 0x1f)) + 63;
					encodedPoints += String.fromCharCode(nextValue);
					dlat >>= 5;
				}
				encodedPoints += String.fromCharCode(dlat + 63);
				
				// The next values will correspond to the longitude for this point.
			    lngE5 = parseInt(points[i][1] * precision);
			    dlng = lngE5 - plng;
			    plng = lngE5;
			    dlng = dlng << 1;
			    
			    if (dlng < 0) {
			    	dlng = ~(dlng);
			    }
			    
			    while (dlng >= 0x20) {
					nextValue = (0x20 | (dlng & 0x1f)) + 63;
					encodedPoints += String.fromCharCode(nextValue);
					dlng >>= 5;
				}
				encodedPoints += String.fromCharCode(dlng + 63);
			}
			return encodedPoints;
		},
		
		mergeTracks : function(geom) {
			if(geom == null) {
				console.log("geom was null!");
				return [];
			}
			
			var points = new Array();
			for (var i = 0; i < geom.length; i++) {
				const pointList = naviki.util.googlepolyline.decode(geom[i], 5);
				for (var counter = 0; counter < pointList.length; counter++) {
					points.push([parseFloat(pointList[counter][0]), parseFloat(pointList[counter][1])]);
				}
			}
			return points;
		},
		
		reverseTrack: function(geom) {
			if(geom == null) {
				console.log("geom was null!");
				return;
			}
			const mergedTracks = naviki.util.googlepolyline.mergeTracks(geom);	
			return mergedTracks.reverse();
		},
		
		/**
		 * 
		 * @param encoded
		 * @param max
		 * @returns string encoded the encoded polyline
		 */
		reduceToMaxPoints : function(encoded, max){		
			var points = naviki.util.googlepolyline.decode(encoded, 5);
			if(points.length > max){
				var newArray = new Array();
				var m = parseInt((points.length / max) + 1);
				
				for(var i = 0; i < points.length; i++){
					if(parseInt((i % m)) == 0){
						newArray.push(points[i]);
					}
				}
				return naviki.util.googlepolyline.encode(newArray, 5);
			}
			return encoded;
		},
		
		
		turnDirection: function(encoded){
			var points = naviki.util.googlepolyline.decode(encoded, 5);
			var newArray = new Array();
			for(var i = (points.length - 1); i >= 0; i--){
				newArray.push(points[i]);
			}
			return naviki.util.googlepolyline.encode(newArray, 5);
		}
};
if (typeof naviki == "undefined") {
	var naviki = {}
}

if (typeof naviki.util == "undefined") {
	naviki.util = {}
}

naviki.util.html = {

	isSanitizerConfigured: boolean = false,

	showMapboxUnsupportedDialog: function(closeCallback) {
		const content = '<b>' + naviki.Lang.getLL('pi_map.error.web_gl_disabled.title')+ '</b><br/>'
						+ naviki.Lang.getLL('pi_map.error.web_gl_disabled.msg');
		const dialog = new $.fn.navikiUiDialog({
			id: "naviki-ui-mapbox-support-dialog",
			content: content,
			className: "edit-dialog",
			closeCallback: closeCallback,
			buttons:[{
				text: naviki.Lang.getLL('base.ok'),
				callback: function() { dialog.close(); }
			}]
		});
		dialog.open();
	},

	/**
	 * The normal bring to top function does not work correctly 
	 * because of the nui- classes. So i must implement ower own.
	 * @returns {Number}
	 */
	bringToTop : function() {
		var aDivs = new Array();
		// get all div elements.
		aDivs = document.getElementsByTagName("div");
		if (aDivs.length == 0) {
			return 10;
		}
		var maxIndex = -9999;
		var count = aDivs.length;
		for (var i = 1; i < count; i++) {
			var div = aDivs[i];
			var sZIndex = Ext.get(div).getStyle("zIndex");
			var nZIndex = (!sZIndex || isNaN(sZIndex)) ? 0 : parseInt(sZIndex,
					10);
			if (nZIndex > maxIndex) {
				maxIndex = nZIndex;
			}
		}
		return maxIndex;
	},

	/**
	 * Adapts the sanitizer's whitelist, analogue to Android and iOS.
	 */
	configureSanitizer: function() {
		HtmlSanitizer.AllowedTags['ABBR'] = false;
		HtmlSanitizer.AllowedTags['CENTER'] = false;
		HtmlSanitizer.AllowedTags['DIV'] = false;
		HtmlSanitizer.AllowedTags['FONT'] = false;
		HtmlSanitizer.AllowedTags['LABEL'] = false;
		HtmlSanitizer.AllowedTags['H1'] = false;
		HtmlSanitizer.AllowedTags['H2'] = false;
		HtmlSanitizer.AllowedTags['H3'] = false;
		HtmlSanitizer.AllowedTags['H4'] = false;
		HtmlSanitizer.AllowedTags['H5'] = false;
		HtmlSanitizer.AllowedTags['H6'] = false;
		HtmlSanitizer.AllowedTags['HR'] = false;
		HtmlSanitizer.AllowedTags['IMG'] = false;
		HtmlSanitizer.AllowedTags['SOURCE'] = false;
		HtmlSanitizer.AllowedTags['TABLE'] = false;
		HtmlSanitizer.AllowedTags['TBODY'] = false;
		HtmlSanitizer.AllowedTags['TR'] = false;
		HtmlSanitizer.AllowedTags['TD'] = false;
		HtmlSanitizer.AllowedTags['TH'] = false;
		HtmlSanitizer.AllowedTags['THEAD'] = false;
		HtmlSanitizer.AllowedTags['UL'] = false;
		HtmlSanitizer.AllowedTags['OL'] = false;
		HtmlSanitizer.AllowedTags['LI'] = false;
		HtmlSanitizer.AllowedTags['VIDEO'] = false;

		HtmlSanitizer.AllowedTags['CODE'] = true;
		HtmlSanitizer.AllowedTags['Q'] = true;
		HtmlSanitizer.AllowedTags['SMALL'] = true;
		HtmlSanitizer.AllowedTags['STRIKE'] = true;

		HtmlSanitizer.AllowedAttributes['style'] = false;
		HtmlSanitizer.AllowedAttributes['cite'] = true;

		HtmlSanitizer.AllowedSchemas['data:'] = false;
		HtmlSanitizer.AllowedSchemas['m-files:'] = false;
		HtmlSanitizer.AllowedSchemas['file:'] = false;
		HtmlSanitizer.AllowedSchemas['pw:'] = false;

		this.isSanitizerConfigured = true;
	},

	escapeHtml: function(unsafe) {
		if (!this.isSanitizerConfigured) {
			this.configureSanitizer();
		}
		return HtmlSanitizer.SanitizeHtml(unsafe);
	},

	br2nl(input) {
		return input.replace(/<br\s*\/?>/mg,"\n");
	},

	nl2br(input) {
		return input.replace(/\n/g, "<br> ");
	},

	/**
	 * Prepare text to display in html area:
	 * - convert <p>-paragraphs to <br>-tags
	 * - escape all html tags for secure output except for <br>-tags
	 * - general output all new lines as <br>, except for lines in lists
	 *
	 * @param text e.g. way descriptions
	 * @return string well-formatted text
	 */
	prepareTextForDisplay(text) {
		var result = this.replaceParagraphWithBreak(text);
		result = this.br2nl(result);
		result = this.escapeHtml(result);
		result = this.nl2br(result);
		return result;
	},

	/**
	 * Replace P tag with Br tag.
	 * @param str
	 * @returns
	 */
	replaceParagraphWithBreak : function(str) {
		str = str.toString();
		str = str.replace(/<p[^>]*?>/gi, "");
		return str.replace("</p>", "<br />");
	},

	wrapUrlsWithLinkTag : function(text) {
		const exp = /((?:^|\s)(https?|ftp|file):\/\/([-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]))/gi;
		return text.replace(exp,"<a href='$1' target='_blank' style='line-break: anywhere;'>$3</a>");
	},

	/**
	 * Substring not in HTML tags.
	 * @param str
	 * @param len
	 * @returns
	 */
	substr : function(str, len) {
		var temp = str.substr(0, len);
		if (temp.lastIndexOf('<') > temp.lastIndexOf('>')) {
			temp = str.substr(0, 1 + str.indexOf('>', temp.lastIndexOf('<')));
		}
		return temp;
	},

	/**
	 * Cut text with use of "..." respecting complete words
	 *
	 * @param text input string
	 * @param limit max length
	 */
	substrWithEllipses: function(text, limit) {
		if (text.length > limit) {
			for (let i = limit; i > 0; i--) {
				if (text.charAt(i) === ' ' && (text.charAt(i-1) !== ',' || text.charAt(i-1) !== '.' || text.charAt(i-1) !== ';')) {
					return text.substring(0, i) + '...';
				}
			}
			return text.substring(0, limit) + '...';
		} else {
			return text;
		}
	}
}
if (typeof naviki == "undefined") {
	var naviki = {}
}

if (typeof naviki.util == "undefined") {
	naviki.util = {}
}

naviki.util.second = {

	toDate : function(timevalue) {
		var mydate = new Date();
		mydate.setTime(timevalue * 1000);
		var month = mydate.getMonth() + 1;
		if (month < 10) {
			month = "0" + month;
		}
		var day = mydate.getDate();
		if (day < 10) {
			day = "0" + day;
		}
		return day + "." + month + "." + mydate.getFullYear();
	},

	toDateAndTime : function(timevalue) {
		var mydate = new Date();
		mydate.setTime(timevalue * 1000);
		var month = mydate.getMonth() + 1;
		if (month < 10) {
			month = "0" + month;
		}
		var day = mydate.getDate();
		if (day < 10) {
			day = "0" + day;
		}
		var minutes = mydate.getMinutes();
		if (minutes < 10) {
			minutes = "0" + minutes;
		}
		var hours = mydate.getHours();
		if (hours < 10) {
			hours = "0" + hours;
		}
		return day + "." + month + "." + mydate.getFullYear() + ", " + hours
				+ ":" + minutes;
	},

	toTime : function(seconds) {
		var hours = Math.floor(seconds / (60 * 60));
		seconds -= hours * (60 * 60);
		var minutes = Math.floor(seconds / 60);
		if (minutes < 10) {
			minutes = "0" + minutes;
		}
		return hours + ":" + minutes;
	}
}
if (typeof naviki == "undefined") {
    var naviki = {}
}

if (typeof naviki.util == "undefined") {
    naviki.util = {}
}

naviki.util.solr = function (config) {
    this.config = config;
    this.initialize();
};

naviki.util.solr.prototype.initialize = function () {
    this.url = this.config.url;
    this.mainCore = this.config.mainCore;
    this.sharedCore = null;
    if (typeof this.config.sharedCore != "undefined" && this.config.sharedCore != null && this.config.sharedCore.length > 0) {
        this.sharedCore = this.config.sharedCore;
    }
};

naviki.util.solr.prototype.getSearchURL = function () {
    this.config.url = naviki.util.url.replaceProtocol(this.config.url);
    return this.config.url + "/solr/" + this.mainCore + "/select";
};

naviki.util.solr.prototype.getReverseSearchURL = function () {
    this.config.url = naviki.util.url.replaceProtocol(this.config.url);
    return this.config.url + "/solr/" + this.mainCore + "/reverse";
};

naviki.util.solr.prototype.getJsonCallback = function () {
    return "json.wrf";
};

naviki.util.solr.prototype.getSearchRequestData = function (query, language, point) {
	const result = {
		"q": query,
		"qt": "default",
		"language": language
	};
	if (point != null) {
        result.pt = point.lat + "," + point.lng;
    }
    if (this.sharedCore != null) {
        result.collection = this.sharedCore + "," + this.mainCore;
    }
    return result;
};

naviki.util.solr.prototype.getReverseSearchRequestData = function (latitude, longitude, language) {
	const pt = latitude + "," + longitude;
	const result = {
		"q": "*:*",
		"fq": "{!geofilt}",
		"pt": pt,
		"d": "5",
		"format": "json",
		"language": language,
		"onlyAddress": true,
		"rows": 1
	};
	if (this.sharedCore != null) {
        result.collection = this.mainCore + "," + this.sharedCore;
    }
    return result;
};

naviki.util.solr.prototype.formatSearchResponse = function (response) {
	const _self = this;
	let docs = response.response;
	if (response.response.docs) {
        docs = response.response.docs;
    }
	return $.map(docs, function (jsonItem) {
		const item = _self.formatResponseItem(jsonItem);
		if (item != null) {
			return item;
		}
	});
};

naviki.util.solr.prototype.formatReverseSearchResponse = function (response) {
    let docs = response.response;
    if (response.response.docs) {
        docs = response.response.docs;
    }
    if (docs.length === 0) {
        return null;
    }
    return this.formatResponseItem(docs[0]);
};

naviki.util.solr.prototype.formatResponseItem = function (item) {
	const coordinate = item.coordinate.split(",");
	return {
		lat: coordinate[0],
		lon: coordinate[1],
		icon: null,
		label: item.label,
		subLabel: item.subLabel
	};
};

if (typeof naviki == "undefined") {
	var naviki = {}
}

if (typeof naviki.util == "undefined") {
	naviki.util = {}
}

naviki.util.url = {

	/**
	 * This method converts a query string to a object.
	 * e.g. 
	 * source -> ?id=10&type=20
	 * target -> {id: 10,type: 20}
	 * 
	 * @param query
	 * @returns {Object}
	 */
	decodeParams : function(query) {

		if (query.indexOf("?") == 0) {
			query = query.substr(1);
		}

		if (query.indexOf("#") == 0) {
			query = query.substr(1);
		}

		var params = {};
		var pairs = query.split('&');
		for (var i = 0; i < pairs.length; i++) {

			var pair = pairs[i].split('=');

			var accessors = [];
			var name = decodeURIComponent(pair[0]), value = decodeURIComponent(pair[1]);

			if (name.length == 0) {
				continue;
			}

			var name = name.replace(/\[([^\]]*)\]/g, function(k, acc) {
				accessors.push(acc);
				return "";
			});
			accessors.unshift(name);

			var obj = null;
			for (var j = accessors.length - 1; j >= 0; j--) {
				var acc = accessors[j];
				if (obj == null) {
					obj = {};
					obj[acc] = value;
				} else {
					var tmp = {};
					tmp[acc] = obj;
					obj = $.extend({}, tmp);
				}
			}
			params = (jQuery).extend(true, {}, params, obj);
		}

		return params;
	},

	/**
	 * This method converts a object to a query string.
	 * e.g.
	 * source -> {id: 10,type: 20}
	 * target -> ?id=10&type=20
	 * 
	 * @param params
	 * @returns
	 */
	encodeParams : function(params) {

		function encodeItem(params, key) {

			var query = "";

			if (typeof params == "object") {

				for (k in params) {

					if (query.length > 0) {
						query += "&";
					}

					query += encodeItem(params[k], key + "[" + k + "]");
				}
			} else {

				query += key + "=" + params;
			}

			return query;
		}
		;

		var query = "";
		var isFirst = true;

		for (key in params) {

			if (query.length > 0) {
				query += "&";
			}

			query += encodeItem(params[key], key);
		}

		return query;
	},

	/**
	 * This method replaces the protocol from a URL.
	 * 
	 * @param url
	 * @returns {String}
	 */
	replaceProtocol : function(url) {
		// do not replace scheme or port if server is "localhost" (e.g. for routing testing)
		if (url.indexOf("://localhost") != -1) {
			console.log("Skip replaceProtocol because it is a development/testing url.")
			return url;
		}

		var result;
		var protocol = window.location.protocol;

		if (url.search(protocol) == -1) {

			var parts = url.split("//");
			var path = parts[parts.length - 1];

			// If the protocol is http, then cut out the port.
			if (protocol == "http:" && path.search(/:/) > 0) {
				parts = path.split(":");
				result = protocol + "//" + parts[0];
				parts = path.split("/");
				for (var i = 1; i < parts.length; i++) {
					result += "/" + parts[i];
				}
			} else {
				result = protocol + "//" + path;
			}
		} else {
			result = url;
		}

		return result;
	},

	/**
	 * This method replaces a value in the url query.
	 * @param keyToReplace
	 * @param value
	 * @returns {String}
	 */
	replaceParam : function(keyToReplace, value) {

		var url = window.location.protocol + "//" + window.location.host
				+ window.location.pathname;
		var params = naviki.util.url.decodeParams(window.location.search);

		params[keyToReplace] = value;

		var query = naviki.util.url.encodeParams(params);
		if (query.length > 0) {
			url += "?" + query + window.location.hash;
		} else {
			url += window.location.hash;
		}

		return url;
	},

	/**
	 * This method removes a key from the url query.
	 * @param keyToRemove
	 * @returns {String}
	 */
	removeParam : function(keyToRemove) {

		var url = window.location.protocol + "//" + window.location.host
				+ window.location.pathname;
		var params = naviki.util.url.decodeParams(window.location.search);

		if (keyToRemove in params) {
			delete params[keyToRemove];
		}

		var query = naviki.util.url.encodeParams(params);
		if (query.length > 0) {
			url += "?" + query + window.location.hash;
		} else {
			url += window.location.hash;
		}

		return url;
	},
	
	/**
	 * This method removes keys from the url query.
	 * @param keyToRemove
	 * @returns {String}
	 */
	removeParams : function(keysToRemove) {

		var url = window.location.protocol + "//" + window.location.host
				+ window.location.pathname;
		var params = naviki.util.url.decodeParams(window.location.search);

		for (var key in params) {
			if(keysToRemove.indexOf(key) != -1) {  
				delete params[key];
			}		
		}

		var query = naviki.util.url.encodeParams(params);
		if (query.length > 0) {
			url += "?" + query + window.location.hash;
		} else {
			url += window.location.hash;
		}

		return url;
	},	

	/**
	 * This method gives back a value from the url query.
	 * @param key
	 * @returns
	 */
	extractValue : function(key) {
		var params = window.location.search, value = "";

		if (params.indexOf(key) < 0) {
			return null;
		}

		value = params.substring(params.indexOf(key), params.length);
		value = value.substring(key.length + 1,
				(value.indexOf("&") > -1) ? value.indexOf("&") : value.length);
		return value;
	},

	/**
	 * This method adds a slash to the end when it is missing.
	 * @param url
	 * @returns the formated url
	 */
	addSlash : function(url) {
		var pos = url.indexOf("/");
		if (pos != 0) {
			url = "/" + url;
		}
		return url;
	},
	
	/**
	 * This method gives back the domain from the url.
	 * @param url
	 * @returns
	 */
	extractDomain: function(url) {
	    var domain = url.replace(/(https?:\/\/)?(www.)?/i, '');
	    domain = domain.split('.');
	    domain = domain.slice(domain.length - 2).join('.');
	    if (domain.indexOf('/') !== -1) {
	        return domain.split('/')[0];
	    }
	    return domain;
	}
}
if (typeof naviki == "undefined") {
	var naviki = {}
}

if (typeof naviki.util == "undefined") {
	naviki.util = {}
}

naviki.util.localeformatter = {

	FACTOR_KCAL_KJ: 4.1868,
	FACTOR_KM_MILES: 0.621371,
	FACTOR_KMH_MPH: 0.621371,
	FACTOR_METER_FEET: 3.28084,

	ENERGY_UNIT_KCAL: 10,
	ENERGY_UNIT_KJ: 20,
	LENGTH_UNIT_UNDEFINED: 0,
	LENGTH_UNIT_METRIC: 10,
	LENGTH_UNIT_IMPERIAL: 20,

	getConvertedEnergy: function(energyInKilocalories) {
		const userEnergyUnit = naviki.util.user.getUserEnergyUnit();
		const factor = (userEnergyUnit != null && userEnergyUnit == this.ENERGY_UNIT_KJ) ? this.FACTOR_KCAL_KJ : 1.0;
		const resultValue = this.round(energyInKilocalories * factor, 0);
		return "" + resultValue + " " + this.getEnergyUnit();
	},

	getEnergyUnit: function() {
		const userEnergyUnit = naviki.util.user.getUserEnergyUnit();
		const isKj = (userEnergyUnit != null && userEnergyUnit == this.ENERGY_UNIT_KJ);
		return naviki.Lang.getLL("pi_userprofile.energy." + (isKj ? "kilojoule" : "kilocalories"));
	},

	getConvertedLength: function(lengthInKilometers, digits) {
		const convValue = this.getConvertedLengthValue(lengthInKilometers);
		const resultValue = this.round(convValue, digits);
		return "" + resultValue + " " + this.getLengthUnit();
	},

	getConvertedLengthValue: function(lengthInKilometers) {
		const factor = (this.getCurrentUserLengthUnit() == this.LENGTH_UNIT_IMPERIAL) ? this.FACTOR_KM_MILES : 1.0;
		return lengthInKilometers * factor;
	},

	getReverseConvertedLengthValue: function(convertedLength) {
		const factor = (this.getCurrentUserLengthUnit() == this.LENGTH_UNIT_IMPERIAL) ? this.FACTOR_KM_MILES : 1.0;
		return convertedLength / factor;
	},

	getLengthUnit: function() {
		const key = (this.getCurrentUserLengthUnit() == this.LENGTH_UNIT_IMPERIAL) ? "base.miles" : "base.kilometre";
		return naviki.Lang.getLL(key);
	},

	getConvertedDistance: function(distanceInMeters, digits) {
		const convValue = this.getConvertedDistanceValue(distanceInMeters);
		const resultValue = this.round(convValue, digits);
		return "" + resultValue + " " + this.getDistanceUnit();
	},

	getConvertedDistanceValue: function(distanceInMeters) {
		const factor = (this.getCurrentUserLengthUnit() == this.LENGTH_UNIT_IMPERIAL) ? this.FACTOR_METER_FEET : 1.0;
		return distanceInMeters * factor;
	},

	getDistanceUnit: function() {
		const key = (this.getCurrentUserLengthUnit() == this.LENGTH_UNIT_IMPERIAL) ? "base.feet" : "base.meter";
		return naviki.Lang.getLL(key);
	},

	getConvertedSpeed: function(speedInKmh, digits) {
		const convValue = this.getConvertedSpeedValue(speedInKmh);
		const resultValue = this.round(convValue, digits);
		return "" + resultValue + " " + this.getSpeedUnit();
	},

	getConvertedSpeedValue: function(speedInKmh) {
		const factor = (this.getCurrentUserLengthUnit() == this.LENGTH_UNIT_IMPERIAL) ? this.FACTOR_KMH_MPH : 1.0;
		return speedInKmh * factor;
	},

	getReverseConvertedSpeedValue: function(convertedSpeed) {
		const factor = (this.getCurrentUserLengthUnit() == this.LENGTH_UNIT_IMPERIAL) ? this.FACTOR_KMH_MPH : 1.0;
		return convertedSpeed / factor;
	},

	getSpeedUnit: function() {
		const key = (this.getCurrentUserLengthUnit() == this.LENGTH_UNIT_IMPERIAL) ? "base.miles.mih" : "base.kilometre.kmh";
		return naviki.Lang.getLL(key);
	},

	isImperial: function() {
		const userLengthUnit = this.getCurrentUserLengthUnit();
		return (userLengthUnit != null && userLengthUnit == this.LENGTH_UNIT_IMPERIAL);
	},

	getCurrentUserLengthUnit: function() {
		var userLengthUnit = naviki.util.user.getUserLengthUnit();

		// when users length unit has status undefined, try to get it locally and persist result to user per API
		// when it is null (still not loaded), do nothing, fallback to metric system
		if ((userLengthUnit == null && !naviki.util.user.isLoggedIn()) || (userLengthUnit != null && userLengthUnit == this.LENGTH_UNIT_UNDEFINED)) {
			userLengthUnit = this.getClientLengthUnit();
			naviki.util.user.setUserLengthUnit(userLengthUnit);
			naviki.util.api.updateUserLengthUnit(userLengthUnit);
		}

		if (userLengthUnit == null) {
			userLengthUnit = this.LENGTH_UNIT_METRIC;
		}

		return userLengthUnit;
	},

	getClientLengthUnit: function() {
		const countries = Array('US', 'LR', 'MM'); // Imperial measurement countries
		var location = window.navigator.language; // "de-DE"

		// If it is GoogleBot, do not trust on (missing) Accept-Language header, instead use page language
		// https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers?hl=de
		if (window.navigator.userAgent.indexOf("Googlebot") !== -1) { // contains
			if (naviki && naviki.CONSTANTS && naviki.CONSTANTS.LOCALE_ALL) {
				const pageLocale = naviki.CONSTANTS.LOCALE_ALL.replace("_","-"); // "de-DE"
				location = pageLocale;
			}
		}

		const country = location.substring(3, 5) // "DE"
		if (countries.indexOf(country) != -1) {
			return this.LENGTH_UNIT_IMPERIAL;
		}
		return this.LENGTH_UNIT_METRIC;
	},

	/**
	 * Rounds a value with <digits> of numbers after the point.
	 */
	round: function(number, digits) {
		// round number
		var factor = Math.pow(10, digits);
		number = (Math.round(number*factor)) / factor;

		// format number
		try {
			const locale = naviki.CONSTANTS.LOCALE_ALL.replace("_","-");
			const options = { minimumFractionDigits: digits, maximumFractionDigits: digits };
			return Number(number).toLocaleString(locale, options);
		} catch (e) {
			console.log("Can not format number: " + number);
			return number;
		}
	}

}
if (typeof naviki == "undefined") {
	var naviki = {};
}
if (typeof naviki.util == "undefined") {
	naviki.util = {};
}

naviki.util.user = {
	getUserProfile: function () {
		return this.getStorageValueFromJson("_n_u_userprofile");
	},

	setUserProfile: function (userProfile) {
		this.setStorageValueAsJson("_n_u_userprofile", userProfile);
	},

	getInitialWayPrivacyValue: function () {
		const userProfile = this.getUserProfile();
		return userProfile != null ? userProfile.wayPrivacy : 0; // fallback = public
	},

	/**
	 * Return a boolean to indicate if current logged in user is allowed to change premium state of routes.
	 * @returns {false|true}
	 */
	hasUserPremiumWayPermission: function () {
		const userProfile = this.getUserProfile();
		return userProfile != null && userProfile.permissionPremiumWay;
	},

	isLoggedIn: function () {
		return this.getUsername() != null && this.getUserId() != null;
	},

	getUsername: function () {
		return this.getStorageValue("_n_u_n");
	},

	setUsername: function (username) {
		this.setStorageValue("_n_u_n", username);
	},

	getUserId: function () {
		return this.getStorageValue("_n_u_id");
	},

	setUserId: function (userId) {
		this.setStorageValue("_n_u_id", userId);
	},

	isActivated: function () {
		const userState = this.getStorageValue("_n_u_act");
		return userState == null ? false : userState == "y" ? true : false;
	},

	setActivated: function (status) {
		// saving of booleans causes problems
		this.setStorageValue("_n_u_act", status ? "y" : "n");
	},

	getUserAverageSpeed: function () {
		const avgSpeed = this.getStorageValue("_n_u_asp");
		return avgSpeed != null ? avgSpeed : 15;
	},

	setUserAverageSpeed: function (avgSpeed) {
		this.setStorageValue("_n_u_asp", avgSpeed);
	},

	getUserEnergyUnit: function () {
		return this.getStorageValue("_n_u_ueu");
	},

	setUserEnergyUnit: function (energyUnit) {
		this.setStorageValue("_n_u_ueu", energyUnit);
	},

	getUserLengthUnit: function () {
		return this.getStorageValue("_n_u_ulu");
	},

	setUserLengthUnit: function (lengthUnit) {
		this.setStorageValue("_n_u_ulu", lengthUnit);
	},

	getUserWayFilters: function () {
		return this.getStorageValueFromJson("_n_u_way_filters");
	},

	setUserWayFilters: function (wayFilters) {
		this.setStorageValueAsJson("_n_u_way_filters", wayFilters);
	},

	getUserWaySort: function () {
		return this.getStorageValue("_n_u_way_sort");
	},

	setUserWaySort: function (waySort) {
		this.setStorageValue("_n_u_way_sort", waySort);
	},

	removeDeprecatedEntries: function () {
		this.setStorageValue("_n_u_s", null);
	},

	getUserProfileImage: function () {
		const userProfile = this.getUserProfile();
		return userProfile != null ? userProfile.profileImage : undefined;
	},

	getStorageValue: function (key) {
		if (typeof localStorage !== "undefined") {
			const storageValue = window.localStorage.getItem(key);
			if (storageValue != "undefined" && storageValue != null) {
				return storageValue;
			}
		}
		return null;
	},

	getStorageValueFromJson: function (key) {
		const storageValue = this.getStorageValue(key);
		return storageValue == null ? null : JSON.parse(storageValue);
	},

	setStorageValue: function (key, storageValue) {
		if (typeof localStorage !== "undefined") {
			if (storageValue == null) {
				window.localStorage.removeItem(key);
			} else {
				window.localStorage.setItem(key, storageValue);
			}
		}
	},

	setStorageValueAsJson: function (key, storageObject) {
		const storageValue =
			storageObject == null ? null : JSON.stringify(storageObject);
		this.setStorageValue(key, storageValue);
	},
};

if (typeof naviki == "undefined") {
	var naviki = {}
}

if (typeof naviki.util == "undefined") {
	naviki.util = {}
}

naviki.util.pixel = {
	
		// distance from a point to a segment between two points
		pointToSegmentDistance:  function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
			return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
		},
		
		closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
			return this._sqClosestPointOnSegment(p, p1, p2);
		},
		
		// return closest point on segment or distance to that point
		_sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
			var x = p1.x,
				y = p1.y,
				dx = p2.x - x,
				dy = p2.y - y,
				dot = dx * dx + dy * dy,
				t;

			if (dot > 0) {
				t = ((p.x - x) * dx + (p.y - y) * dy) / dot;

				if (t > 1) {
					x = p2.x;
					y = p2.y;
				} else if (t > 0) {
					x += dx * t;
					y += dy * t;
				}
			}

			dx = p.x - x;
			dy = p.y - y;

			return sqDist ? dx * dx + dy * dy : {x:x, y:y};
		},
		
		// square distance (to avoid unnecessary Math.sqrt calls)
		dist: function (p1, p2) {
			var xs = p2.x - p1.x,
				ys = p2.y - p1.y;		
		
			xs *= xs;
			ys *= ys;
		 
			return Math.sqrt( xs + ys );
		},
}
/*
 *  jQuery Boilerplate - v3.3.1
 *  A jump-start for jQuery plugins development.
 *  http://jqueryboilerplate.com
 *
 *  Made by Zeno Rocha
 *  Under MIT License
 */
// the semi-colon before function invocation is a safety net against concatenated
// scripts and/or other plugins which may not be closed properly.
;(function($, window, document, undefined) {

	var pluginName = "History";
	
	function Plugin(settings){
		
		var defaults = {
			popstate: null
		};
		
		if (settings){
			$.extend(this, defaults, settings);
		} else {
			$.extend(this, defaults);
		}
		
		this.enable = false;
		this.init();
	}
	
	Plugin.prototype = {
		
		init : function(){
			
			var _self = this;
			
			/*
	         * Note, this is the only difference when using this library,
	         * because the object window.location cannot be overriden,
	         * so library the returns generated "location" object within
	         * an object window.history, so get it out of "history.location".
	         * For browsers supporting "history.pushState" get generated
	         * object "location" with the usual "window.location".
	         */
	        var location = window.history.location || window.location;
	        
	        $(window).on('popstate', function(e) {
	        	try {
		        	if (typeof _self.popstate == "function") {
	        			_self.popstate(history.state);
	        		}
	        	} catch (e) {
	        		console.log(e);
	        	}
	       });
		},
	
		replaceState: function(state, title, url) {
			try {
				if (typeof history != "undefined") {
					if (state == null) {
						state = this.getState();
					}
					history.replaceState(state, title, url);
				}
			} catch (e) {
				console.log(e);
			}
		},
		
		pushState: function(state, title, url) {
			try {
				if (typeof history != "undefined") {
					history.pushState(state, title, url);
				}
			} catch (e) {
				console.log(e);
			}
		},
		
		updateURL: function(url) {
			try {
				if (typeof history != "undefined") {
					history.replaceState(this.getState(), "", url);
				}
			} catch (e) {
				console.log(e);
			}
		},
		
		getState: function() {
			try {
				if(typeof history != "undefined") {
					return history.state;
				}
			} catch (e) {
				console.log(e);
			}
			return null;
			
		},
		
		back: function() {
			try {
				if(typeof history != "undefined") {
					history.back();
				}
			} catch (e) {
				console.log(e);
			}
		}
	}
		
	$.fn[pluginName] = function(options) {
		return new Plugin(options);
	}

})(jQuery, window, document);
/**
 * Call function if user clicked on naviki logo.
 * Changes the window location to the start of the landingpage.
 * 
 */
;(function($) {
	$.fn.navikiLogo = function(landingpageTitle) {
		var hrefParts = window.location.href.split('/');
		if (hrefParts[3].length == 2) {
			// we have a language parameter
			window.location.href = hrefParts[0]+"//"+hrefParts[2]+"/"+hrefParts[3]+"/"+landingpageTitle+"/";
		} else {
			window.location.href = hrefParts[0]+"//"+hrefParts[2]+"/"+landingpageTitle+"/";
		}
	};
})( jQuery);
/*
 *  jQuery Boilerplate - v3.3.1
 *  A jump-start for jQuery plugins development.
 *  http://jqueryboilerplate.com
 *
 *  Made by Zeno Rocha
 *  Under MIT License
 */
// the semi-colon before function invocation is a safety net against concatenated
// scripts and/or other plugins which may not be closed properly.
;(function ($, window, document, undefined) {

    var pluginName = "navikiUiDialog";
    var defaults = {
        id: "naviki-ui-dialog",
        close: true, // should show close-icon on top right
        content: "",
        footer: "",
        buttons: null,
        className: null,
        closeCallback: null,
        openCallback: null
    };

    function Plugin(options) {
        this.options = $.extend({}, defaults, options);
        this._defaults = defaults;
        this._name = pluginName;

        this.element = null;
        this.init();
    }

    Plugin.prototype = {

        init: function () {
            if ($("#" + this.options.id).length == 0) {
                this.element = $("<div/>", {id: this.options.id, "class": "naviki-ui-dialog-wrapper"});
                $('body').append(this.element);

                var dialog = $("<div/>", {"class": "naviki-ui-dialog"});
                this.element.append(dialog);

                var container = $("<div/>", {"class": "dialog-container"});
                dialog.append(container);

                container.append($("<div/>", {"class": "header"}));
                container.append($("<div/>", {"class": "content"}));
                container.append($("<div/>", {"class": "buttons"}));
                container.append($("<div/>", {"class": "footer"}));

                $(document).keyup(function(e) {
                    if (e.keyCode == 27 && this.isShown()) { // escape button
                        this.close();
                    }
                }.bind(this));

                if (this.options.className != null) {
                    this.element.addClass(this.options.className);
                }
            } else {
                this.element = $("#" + this.options.id);
            }

            this.element.find('.header').first().empty();
            this.element.find('.buttons').first().empty();
            this.element.find('.content').first().html(this.options.content);
            this.element.find('.footer').first().html(this.options.footer);

            if (this.options.footer.length == 0) {
                this.element.find('.footer').first().css("display", "none");
            } else {
                this.element.find('.footer').first().css("display", "");
            }

            if (this.options.close) {
                this.element.find('.header').first().append($("<span/>", {"class": "icon-close"}));
                this.element.find('.icon-close').first().click(this.close.bind(this));
            }

            if (this.options.buttons != null && $.isArray(this.options.buttons) && this.options.buttons.length > 0) {

                for (var i = 0; i < this.options.buttons.length; i++) {
                    (function (button) {
                        const btn = $("<button/>", {type: "button", "class": "btn btn-default"});
                        btn.text(button.text);
                        btn.click(button.callback);
                        this.element.find('.buttons').first().append(btn);
                    }).bind(this)(this.options.buttons[i]);
                }
            }

            $(window).resize(this.resize.bind(this));
        },

        open: function () {
            if (this.isShown()) {
                return;
            }

            this.element.addClass('visible');
            this.resize();
            if (typeof this.options.openCallback == "function") {
                this.options.openCallback();
            }
        },

        close: function () {
            if (!this.isShown()) {
                return;
            }

            this.element.removeClass('visible');
            if (typeof this.options.closeCallback == "function") {
                this.options.closeCallback();
            }
        },

        isShown: function () {
            return this.element.hasClass('visible');
        },

        resize: function () {
            if (!this.isShown()) {
                return;
            }

            const content = this.element.find('.content').first();
            content.css("height", "");

            const height = content.first().outerHeight();
            const windowHeight = $(window).outerHeight() * 0.9;
            if (height > windowHeight) {
                content.css("height", windowHeight);
            }
        }

    };

    $.fn[pluginName] = function (options) {
        return new Plugin(options);
    }

})(jQuery, window, document);
/*
 *  jQuery Boilerplate - v3.3.1
 *  A jump-start for jQuery plugins development.
 *  http://jqueryboilerplate.com
 *
 *  Made by Zeno Rocha
 *  Under MIT License
 */
// the semi-colon before function invocation is a safety net against concatenated
// scripts and/or other plugins which may not be closed properly.
;(function ( $, window, document, undefined ) {

		// undefined is used here as the undefined global variable in ECMAScript 3 is
		// mutable (ie. it can be changed by someone else). undefined isn't really being
		// passed in so we can ensure the value of it is truly undefined. In ES5, undefined
		// can no longer be modified.

		// window and document are passed through as local variable rather than global
		// as this (slightly) quickens the resolution process and can be more efficiently
		// minified (especially when both are regularly referenced in your plugin).

		// Create the defaults once
		var pluginName = "navikiUiLoading";
		var defaults = {
				id : null,
				zindex: null
		};

		// The actual plugin constructor
		function Plugin ( element, options ) {
				
				this.calcualte = function(){				
					$("#loader-overlay-" + this.options.id).css({
						opacity : 0.15,
						top : $(this.element).position().top,
						left: $(this.element).position().left,
						width : $(this.element).outerWidth(),
						height: $(this.element).outerHeight()
					});
					$("#loader-img-" + this.options.id).css({
						top : ($(this.element).height() / 2 - 9),
						left: ($(this.element).width() / 2 - 9)
					});
				};
				
				var uniqID = {
					counter:0,
					get:function(prefix) {
						if(!prefix) {
							prefix = "uniqid";
						}
						var id =  prefix+""+uniqID.counter++;
						if (jQuery("#"+id).length == 0) {
							return id;
						} else {
							return uniqID.get()
						}
					}
				}

				this.element = element;
				// jQuery has an extend method which merges the contents of two or
				// more objects, storing the result in the first object. The first object
				// is generally empty as we don't want to alter the default options for
				// future instances of the plugin
				this.options = $.extend( {}, defaults, options );
				this.options.id = uniqID.get();
				this._defaults = defaults;
				this._name = pluginName;
				this.init();
		}

		Plugin.prototype = {
				init: function () {
					// Place initialization logic here
					// You already have access to the DOM element and
					// the options via the instance, e.g. this.element
					// and this.options
					// you can add more functions like the one below and
					// call them like so: this.yourOtherFunction(this.element, this.options).		
					//this.options.id = $(this.element).uniqueId().attr( "id" );
					var _self = this;
					var node = "<div id='loader-overlay-" + this.options.id + "' class='naviki-ui-loader-overlay'";
					if(this.options.zindex != null) {
						node += " style='z-index:" + this.options.zindex + ";'";
					}
					node += ">" +
					"<img class='naviki-ui-loader-img' src='typo3conf/ext/naviki/Resources/Public/Img/ajax-loader-2.gif' id='loader-img-" + this.options.id + "' />" +
					"</div>";		
					$(this.element).append(node);
					$(window).resize(function() {
						_self.calcualte();
					});
				},
				hide : function(args) {
					$("#loader-overlay-" + this.options.id).fadeOut(0);
				},
				show : function(args) {
					this.calcualte();
					$("#loader-overlay-" + this.options.id).fadeIn(0);
				}
		};

		// A really lightweight plugin wrapper around the constructor, 
		// preventing against multiple instantiations and allowing any
		// public function (ie. a function whose name doesn't start
		// with an underscore) to be called via the jQuery plugin,
		// e.g. $(element).defaultPluginName('functionName', arg1, arg2)
		$.fn[ pluginName ] = function ( options ) {
			var args = arguments;
			if (options === undefined || typeof options === 'object') {
				return this.each(function () {
		            if (!$.data(this, 'plugin_' + pluginName)) {
		                $.data(this, 'plugin_' + pluginName, new Plugin( this, options ));
		            }
		        });
		    } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
		        return this.each(function () {
		            var instance = $.data(this, 'plugin_' + pluginName);
		            if (instance instanceof Plugin && typeof instance[options] === 'function') {
		                instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) );
		            }
		        });
		    }
		};

})( jQuery, window, document );
/* jQuery Boilerplate - v3.3.1 */
;(function ( $, window, document, undefined ) {

		var pluginName = "navikiUiUserActivationHint";
		var defaults = {
				oauth2MicroServiceHostname: ""
		};

		function Plugin (options) {
				this.options = $.extend( {}, defaults, options );
				this._defaults = defaults;
				this._name = pluginName;
				
				this.element = null;
				this.init();
		}

		Plugin.prototype = {
				init: function () {
					if (naviki.util.user.isLoggedIn() && !naviki.util.user.isActivated()) {
						this._requestActivationStatus();
					}
				},
				
				_showUserActivationHintBar: function(email) {
					var _this = this;

					var hintText = naviki.Lang.getLL('base.deactivatedAccountHint');
					hintText = hintText.replace("{0}", email);

					var linkUrl = this.options.oauth2MicroServiceHostname + "/reactivateUser";
					if (naviki.util.user.getUsername() != null)
						linkUrl += "?emailPlaceholder=" + naviki.util.user.getUsername();

					var content = "<div class='cookie-choice-info warning'><div>";
					content += "<span>" + hintText + "</span>";
					content += "<a href='" + linkUrl + "' id='user-activation-resend-button'>" + naviki.Lang.getLL('base.deactivatedAccountResendButton') + "</a>";
					content += "<a href='" + naviki.CONSTANTS.LANGUAGE + "/naviki/intern/my-naviki-profile-and-settings/'>" + naviki.Lang.getLL('base.deactivatedAccountChangeEmailButton') + "</a>";
					content += "</div></div>";
						
					if ($('#fixedHeader').length > 0) {
						$('#fixedHeader').prepend(content);
					} else {
						$('body').prepend(content);
					}

					$("#user-activation-resend-button").click(function(event) {
						event.preventDefault();
						var url = $(this).attr("href");
						_this._openDialog(url);	
					});	
				},
		 		
		 		_requestActivationStatus: function() {
					var _this = this;
					$.ajax({
						url: this.options.oauth2MicroServiceHostname + "/api/user/activatedAccount",
						type: "POST",
						cache: false,
						dataType : "json",
						contentType: 'application/json; charset=utf-8',
					    processData: false,
					    beforeSend: function(xhr) {
							if (naviki.util.api.getAccessToken() != null) {
								xhr.setRequestHeader('Authorization', 'Bearer ' + naviki.util.api.getAccessToken());
							}
					    },
						success: function(data) {
							if (typeof data.valid !== 'undefined') {
								if (data.valid) {
									naviki.util.user.setActivated(true);
								} else {
									_this._showUserActivationHintBar(data.email);
								}
							}
						},
						error: function (request, status, error) {
							console.log("Error in jquery.naviki.ui.useractivationhint.js: " + error);
		                }
					});
				},
				
				_openDialog: function(url) {	
					this.dialog = $.colorbox({
						width: 320, 
						height: 480, 
						scrolling: false,
						iframe: true, 
						href: url
					});
				}
		};

		$.fn[pluginName] = function(options) {
			return new Plugin(options);
		}
})( jQuery, window, document );
/*
 *  jQuery Boilerplate - v3.3.1
 *  A jump-start for jQuery plugins development.
 *  http://jqueryboilerplate.com
 *
 *  Made by Zeno Rocha
 *  Under MIT License
 */
// the semi-colon before function invocation is a safety net against concatenated
// scripts and/or other plugins which may not be closed properly.
;
(function($, window, document, undefined) {

	// undefined is used here as the undefined global variable in ECMAScript 3 is
	// mutable (ie. it can be changed by someone else). undefined isn't really being
	// passed in so we can ensure the value of it is truly undefined. In ES5, undefined
	// can no longer be modified.

	// window and document are passed through as local variable rather than global
	// as this (slightly) quickens the resolution process and can be more efficiently
	// minified (especially when both are regularly referenced in your plugin).

	// Create the defaults once
	var pluginName = "navikiUserAuth";
	var defaults = {};

	// The actual plugin constructor
	function Plugin(element, options) {

		this.element = element;
		// jQuery has an extend method which merges the contents of two or
		// more objects, storing the result in the first object. The first object
		// is generally empty as we don't want to alter the default options for
		// future instances of the plugin
		this.options = $.extend({}, defaults, options);
		this._defaults = defaults;
		this._name = pluginName;
		
		this.history = new $.fn.History();
		this.logoutDialog = null;
		this.loginDialog = null;
		this.loginDialogWidth = 320;
		this.loginDialogHeight = 480;
		
		this.init();
	}

	Plugin.prototype = {

		init : function() {
			// Place initialization logic here
			// You already have access to the DOM element and
			// the options via the instance, e.g. this.element
			// and this.options
			// you can add more functions like the one below and
			// call them like so: this.yourOtherFunction(this.element, this.options).		
			var _this = this;	
			
			// remove unused item
			if(typeof(localStorage) !== "undefined") {	
				window.localStorage.removeItem("_n_um");
				window.localStorage.removeItem("_n_un");
				window.localStorage.removeItem("_n_us");
			}
			
			if (window.location != window.parent.location) { 
				// THE PAGE IS OPENED INSIDE A IFRAME
				// event listener for cross origin communication,
				window.addEventListener("message", function(event) {
					
					// Do we trust the sender of this message?
				    var domain = naviki.util.url.extractDomain(event.origin);
				    if (domain !== "localhost:18080" &&
				        domain !== "naviki.org") {
				        return;
				    }
					
					if (event.data.get == "search") {
						event.source.postMessage({search:window.location.search},  event.origin);
					}
				}, false);	
			} else {
				// THE PAGE IS OPENED INSIDE A BROWSER TAB
				// event listener for cross origin communication,
				window.addEventListener("message", function(event) {
					
					// Do we trust the sender of this message?
				    var domain = naviki.util.url.extractDomain(event.origin);
				    if (domain !== "localhost:18080" &&
				        domain !== "naviki.org") {
				        return;
				    }	
					
					if (_this.loginDialog != null) {
						if (event.data.height) {
							//enough height for g-recaptcha
							const newHeight = event.data.height < 522 ? 522 : event.data.height;
							_this.loginDialog.colorbox.resize({
								innerWidth: _this.loginDialogWidth,
								innerHeight: newHeight
							});
						}
						if (event.data.search) {
							var query = naviki.util.url.decodeParams(decodeURI(event.data.search.substring(1)));
							_this.initLogin(query);
							setTimeout(function(){
								_this.loginDialog.colorbox.close();
								_this.loginDialog.colorbox.remove();
							}, 3000);
						}
					}
				}, false);			

				// open the login/register page inside a iframe
				$("#header-register-container a").first().click(function(event) {
					event.preventDefault();

					// NAV-7526 - on mobile devices redirect to map page
					if (_this.isMobileView()) {
						// first page in menu is usually the map which contains the mobile container ("open naviki app")
						_this.triggerFirstMainMenuSubPage();
						return;
					}

					const url = $(this).attr("href");
					_this.openLoginDialog(url);	
				});

				$("#header-login-container a").first().click(function(event) {
					event.preventDefault();

					// NAV-7526 - on mobile devices redirect to map page
					if (_this.isMobileView()) {
						// first page in menu is usually the map which contains the mobile container ("open naviki app")
						_this.triggerFirstMainMenuSubPage();
						return;
					}

					const url = $(this).attr("href");
					_this.openLoginDialog(url);
				});	

				// catch url query parameters as object
				var query = naviki.util.url.decodeParams(decodeURI(window.location.search.substring(1)));	
				this.initLogin(query);
			}
		},
		
		initLogin: function(query) {
			if (query.login != undefined) {
				// catch typo3 sociallogin
				this.login(null);
				// remove GET params
				var url = naviki.util.url.removeParam("login");
				this.history.replaceState(null, null, url);
			} else if (query.code) {
				// catch java oauth2
				this.login(query.code);
				// remove GET params
				var url = naviki.util.url.removeParams(new Array("code","state"));
				this.history.replaceState(null,null,url);
			} else if(naviki.util.user.isLoggedIn() && naviki.util.user.getUsername() != null) {
				this.showMenuBtn();
			} else {
				this.showLoginBtn();
			}
		},
		
		login: function(code) {
			var params = "?request[format]=json&request[controller]=FeUser&request[action]=feSession";
			params += "&request[arguments][code]=" + code;

			$.ajax({
				url: naviki.util.api.getAjaxDispatcherBasePath() + encodeURI(params),
				type: "GET",
				cache: false,
				dataType : "json",
				contentType: 'application/json; charset=utf-8',
			    processData: false,
				success : function(data) {
					if (data.status) {	
						naviki.util.api.setAccessToken(data.accessToken);
						naviki.util.api.setRefreshToken(data.refreshToken);
						naviki.util.user.setUsername(data.username);
						naviki.util.user.setUserId(data.feUserId);
						this.showMenuBtn();

						naviki.util.api.loadUserProfile();
					} else {
						_this.showLoginBtn();
					}
					$(window).trigger("naviki_loginSuccess");

					// when user has successfully changed password, it comes to redirect page and this point
					// user is logged in now and tokens are set
					// in this case, wer must manually redirect to naviki start page
					if (window.location.pathname.indexOf("/naviki/single-pages/loading/mobile.html") != -1) {
						document.location.replace(location.origin);
					}
				}.bind(this),
				beforeSend: this.clear.bind(this),
				complete: function() {},
				error: this.showLoginBtn.bind(this)
			});
		},

		logout: function(targetUrl) {
			this.logoutDialog = new $.fn.navikiUiDialog({
				id: "naviki-ui-logout-dialog",
				content: naviki.Lang.getLL('pi_userprofile.logoutDialog'),
				buttons:[{
					text: naviki.Lang.getLL('base.yes'),
					callback: function() {
						this.logoutUser(targetUrl);
					}.bind(this)
				},{
					text: naviki.Lang.getLL('base.no'),
					callback: this.closeLogoutDialog.bind(this)
				}]
			});
			this.logoutDialog.open();
		},

		closeLogoutDialog: function() {
			if (this.logoutDialog != null) {
				this.logoutDialog.close();
			}
		},
		
		showLoginBtn: function() {
			this.clear();
			$("#header-dropdown-container").fadeOut("slow",function(){
				$("#header-login-container").fadeIn("slow");
				$("#header-register-container").fadeIn("slow");
			});
		},
		
		showMenuBtn: function(loadItems) {
			$("#header-register-container").fadeOut("slow");
			
			$("#header-login-container").fadeOut("slow",function() {
				$("#header-username").text(naviki.util.user.getUsername());
				$("#header-dropdown-container").fadeIn("slow");
				const profileImage = naviki.util.user.getUserProfileImage();
				if (profileImage != undefined) {
					$("#header-avatar")
						.attr("src", profileImage)
						.on("load", function () {
							$("#header-avatar-placeholder").fadeOut("slow");
							$("#header-avatar").fadeIn("slow");
						});
				}
			});

			$("#header-dropdown-btn").one("click",this.loadMenu.bind(this));
		},
		
		loadMenu: function() {
			const params = "?request[format]=json&request[controller]=FeUser&request[action]=feMenu";
			$.ajax({
				url: naviki.util.api.getAjaxDispatcherBasePath() + encodeURI(params),
				type: "GET",
				cache: false,
				dataType : "json",
				contentType: 'application/json; charset=utf-8',
			    processData: false,
				success : function(data) {
					if (data.status) {
						$("#header-dropdown-menu").html(data.menuItems);
						this.initLogoutButton();
					} else {
						this.showLoginBtn();
					}
				}.bind(this),
				beforeSend: function() {
					$("#header-caret").css("visibility", "hidden");
					$("#header-loading").addClass("spinning-wheel");
					$("#header-dropdown-menu").css("border-width","0");
				},
				complete: function() {
					$("#header-caret").css("visibility", "");
					$("#header-loading").removeClass("spinning-wheel");
					$("#header-dropdown-menu").css("border-width","1px");
				},
				error: function(jqXHR, textStatus, errorThrown) {
					// do not logout if server was too busy
					if (jqXHR != null && jqXHR.status != null && jqXHR.status == 504) {
						return;
					}
					this.showLoginBtn();
				}.bind(this)
			});
		},

		initLogoutButton: function() {
			const logoutButton = $("#header-dropdown-container ul li:last-child")
			logoutButton.on("click", function(event) {
				event.preventDefault();
				const targetUrl = logoutButton.find("a:first-child").attr("href");
				this.logout(targetUrl);
			}.bind(this));
		},
		
		clear: function() {
			naviki.util.api.logoutFeUser();
		},

		logoutUser: function(targetUrl) {
			const params = "?request[format]=json&request[controller]=FeUser&request[action]=logout";
			naviki.util.api.request({
				url: naviki.util.api.getAjaxDispatcherBasePath() + encodeURI(params),
				type: "GET",
				cache: false,
				dataType : "json",
				contentType: 'application/json; charset=utf-8',
				processData: false,
				success : function(data) { console.log(data); },
				beforeSend: function() {
					$("#naviki-ui-logout-dialog").navikiUiLoading();
					$("#naviki-ui-logout-dialog").navikiUiLoading("show");
				},
				complete: function() {
					$("#naviki-ui-logout-dialog").navikiUiLoading("hide");
					this.closeLogoutDialog();

					// always continue logout
					this.clear();
					location.href = targetUrl
				}.bind(this),
				error: function(error) { console.log(error); }
			});
		},
		
		openLoginDialog: function(url) {	
			this.loginDialog = $.colorbox({
				width: this.loginDialogWidth,
				height: this.loginDialogHeight,
				scrolling: false,
				iframe: true, 
				href: url,
				onComplete : function() {
					// avoid opaque title bar
					$('#colorbox').css("background", "#ffffff");
					
					var frame = $('#colorbox iframe').get(0);
					$(frame).load(function(e) {
						frame.contentWindow.postMessage({get:"height"}, "*");
						frame.contentWindow.postMessage({get:"search"}, "*");
					});
				} 
			});
		},

		isMobileView: function() {
			// because of css rules like @media (max-width: 767px)
			return $(window).width() <= 767;
		},

		triggerFirstMainMenuSubPage: function() {
			// native click() on a-tag
			$('div#navbar div.main-nav:first ul.nav:first li:first a:first')[0].click();
		}
	};
	
	// A really lightweight plugin wrapper around the constructor, 
	// preventing against multiple instantiations and allowing any
	// public function (ie. a function whose name doesn't start
	// with an underscore) to be called via the jQuery plugin,
	// e.g. $(element).defaultPluginName('functionName', arg1, arg2)
	$.fn[pluginName] = function(options) {
		var args = arguments;
		if (options === undefined || typeof options === 'object') {
			return this.each(function() {
				if (!$.data(this, 'plugin_' + pluginName)) {
					$.data(this, 'plugin_' + pluginName, new Plugin(this, options));
				}
			});
		} else if (typeof options === 'string' && options[0] !== '_'
				&& options !== 'init') {
			return this.each(function() {
				var instance = $.data(this, 'plugin_' + pluginName);
				if (instance instanceof Plugin && typeof instance[options] === 'function') {
					instance[options].apply(instance, Array.prototype.slice .call(args, 1));
				}
			});
		}
	};

})(jQuery, window, document);

/*!
	Colorbox 1.6.4
	license: MIT
	http://www.jacklmoore.com/colorbox
*/
(function(t,e,i){function n(i,n,o){var r=e.createElement(i);return n&&(r.id=Z+n),o&&(r.style.cssText=o),t(r)}function o(){return i.innerHeight?i.innerHeight:t(i).height()}function r(e,i){i!==Object(i)&&(i={}),this.cache={},this.el=e,this.value=function(e){var n;return void 0===this.cache[e]&&(n=t(this.el).attr("data-cbox-"+e),void 0!==n?this.cache[e]=n:void 0!==i[e]?this.cache[e]=i[e]:void 0!==X[e]&&(this.cache[e]=X[e])),this.cache[e]},this.get=function(e){var i=this.value(e);return t.isFunction(i)?i.call(this.el,this):i}}function h(t){var e=W.length,i=(A+t)%e;return 0>i?e+i:i}function a(t,e){return Math.round((/%/.test(t)?("x"===e?E.width():o())/100:1)*parseInt(t,10))}function s(t,e){return t.get("photo")||t.get("photoRegex").test(e)}function l(t,e){return t.get("retinaUrl")&&i.devicePixelRatio>1?e.replace(t.get("photoRegex"),t.get("retinaSuffix")):e}function d(t){"contains"in x[0]&&!x[0].contains(t.target)&&t.target!==v[0]&&(t.stopPropagation(),x.focus())}function c(t){c.str!==t&&(x.add(v).removeClass(c.str).addClass(t),c.str=t)}function g(e){A=0,e&&e!==!1&&"nofollow"!==e?(W=t("."+te).filter(function(){var i=t.data(this,Y),n=new r(this,i);return n.get("rel")===e}),A=W.index(_.el),-1===A&&(W=W.add(_.el),A=W.length-1)):W=t(_.el)}function u(i){t(e).trigger(i),ae.triggerHandler(i)}function f(i){var o;if(!G){if(o=t(i).data(Y),_=new r(i,o),g(_.get("rel")),!U){U=$=!0,c(_.get("className")),x.css({visibility:"hidden",display:"block",opacity:""}),I=n(se,"LoadedContent","width:0; height:0; overflow:hidden; visibility:hidden"),b.css({width:"",height:""}).append(I),j=T.height()+k.height()+b.outerHeight(!0)-b.height(),D=C.width()+H.width()+b.outerWidth(!0)-b.width(),N=I.outerHeight(!0),z=I.outerWidth(!0);var h=a(_.get("initialWidth"),"x"),s=a(_.get("initialHeight"),"y"),l=_.get("maxWidth"),f=_.get("maxHeight");_.w=Math.max((l!==!1?Math.min(h,a(l,"x")):h)-z-D,0),_.h=Math.max((f!==!1?Math.min(s,a(f,"y")):s)-N-j,0),I.css({width:"",height:_.h}),J.position(),u(ee),_.get("onOpen"),O.add(F).hide(),x.focus(),_.get("trapFocus")&&e.addEventListener&&(e.addEventListener("focus",d,!0),ae.one(re,function(){e.removeEventListener("focus",d,!0)})),_.get("returnFocus")&&ae.one(re,function(){t(_.el).focus()})}var p=parseFloat(_.get("opacity"));v.css({opacity:p===p?p:"",cursor:_.get("overlayClose")?"pointer":"",visibility:"visible"}).show(),_.get("closeButton")?B.html(_.get("close")).appendTo(b):B.appendTo("<div/>"),w()}}function p(){x||(V=!1,E=t(i),x=n(se).attr({id:Y,"class":t.support.opacity===!1?Z+"IE":"",role:"dialog",tabindex:"-1"}).hide(),v=n(se,"Overlay").hide(),L=t([n(se,"LoadingOverlay")[0],n(se,"LoadingGraphic")[0]]),y=n(se,"Wrapper"),b=n(se,"Content").append(F=n(se,"Title"),R=n(se,"Current"),P=t('<button type="button"/>').attr({id:Z+"Previous"}),K=t('<button type="button"/>').attr({id:Z+"Next"}),S=t('<button type="button"/>').attr({id:Z+"Slideshow"}),L),B=t('<button type="button"/>').attr({id:Z+"Close"}),y.append(n(se).append(n(se,"TopLeft"),T=n(se,"TopCenter"),n(se,"TopRight")),n(se,!1,"clear:left").append(C=n(se,"MiddleLeft"),b,H=n(se,"MiddleRight")),n(se,!1,"clear:left").append(n(se,"BottomLeft"),k=n(se,"BottomCenter"),n(se,"BottomRight"))).find("div div").css({"float":"left"}),M=n(se,!1,"position:absolute; width:9999px; visibility:hidden; display:none; max-width:none;"),O=K.add(P).add(R).add(S)),e.body&&!x.parent().length&&t(e.body).append(v,x.append(y,M))}function m(){function i(t){t.which>1||t.shiftKey||t.altKey||t.metaKey||t.ctrlKey||(t.preventDefault(),f(this))}return x?(V||(V=!0,K.click(function(){J.next()}),P.click(function(){J.prev()}),B.click(function(){J.close()}),v.click(function(){_.get("overlayClose")&&J.close()}),t(e).bind("keydown."+Z,function(t){var e=t.keyCode;U&&_.get("escKey")&&27===e&&(t.preventDefault(),J.close()),U&&_.get("arrowKey")&&W[1]&&!t.altKey&&(37===e?(t.preventDefault(),P.click()):39===e&&(t.preventDefault(),K.click()))}),t.isFunction(t.fn.on)?t(e).on("click."+Z,"."+te,i):t("."+te).live("click."+Z,i)),!0):!1}function w(){var e,o,r,h=J.prep,d=++le;if($=!0,q=!1,u(he),u(ie),_.get("onLoad"),_.h=_.get("height")?a(_.get("height"),"y")-N-j:_.get("innerHeight")&&a(_.get("innerHeight"),"y"),_.w=_.get("width")?a(_.get("width"),"x")-z-D:_.get("innerWidth")&&a(_.get("innerWidth"),"x"),_.mw=_.w,_.mh=_.h,_.get("maxWidth")&&(_.mw=a(_.get("maxWidth"),"x")-z-D,_.mw=_.w&&_.w<_.mw?_.w:_.mw),_.get("maxHeight")&&(_.mh=a(_.get("maxHeight"),"y")-N-j,_.mh=_.h&&_.h<_.mh?_.h:_.mh),e=_.get("href"),Q=setTimeout(function(){L.show()},100),_.get("inline")){var c=t(e).eq(0);r=t("<div>").hide().insertBefore(c),ae.one(he,function(){r.replaceWith(c)}),h(c)}else _.get("iframe")?h(" "):_.get("html")?h(_.get("html")):s(_,e)?(e=l(_,e),q=_.get("createImg"),t(q).addClass(Z+"Photo").bind("error."+Z,function(){h(n(se,"Error").html(_.get("imgError")))}).one("load",function(){d===le&&setTimeout(function(){var e;_.get("retinaImage")&&i.devicePixelRatio>1&&(q.height=q.height/i.devicePixelRatio,q.width=q.width/i.devicePixelRatio),_.get("scalePhotos")&&(o=function(){q.height-=q.height*e,q.width-=q.width*e},_.mw&&q.width>_.mw&&(e=(q.width-_.mw)/q.width,o()),_.mh&&q.height>_.mh&&(e=(q.height-_.mh)/q.height,o())),_.h&&(q.style.marginTop=Math.max(_.mh-q.height,0)/2+"px"),W[1]&&(_.get("loop")||W[A+1])&&(q.style.cursor="pointer",t(q).bind("click."+Z,function(){J.next()})),q.style.width=q.width+"px",q.style.height=q.height+"px",h(q)},1)}),q.src=e):e&&M.load(e,_.get("data"),function(e,i){d===le&&h("error"===i?n(se,"Error").html(_.get("xhrError")):t(this).contents())})}var v,x,y,b,T,C,H,k,W,E,I,M,L,F,R,S,K,P,B,O,_,j,D,N,z,A,q,U,$,G,Q,J,V,X={html:!1,photo:!1,iframe:!1,inline:!1,transition:"elastic",speed:300,fadeOut:300,width:!1,initialWidth:"600",innerWidth:!1,maxWidth:!1,height:!1,initialHeight:"450",innerHeight:!1,maxHeight:!1,scalePhotos:!0,scrolling:!0,opacity:.9,preloading:!0,className:!1,overlayClose:!0,escKey:!0,arrowKey:!0,top:!1,bottom:!1,left:!1,right:!1,fixed:!1,data:void 0,closeButton:!0,fastIframe:!0,open:!1,reposition:!0,loop:!0,slideshow:!1,slideshowAuto:!0,slideshowSpeed:2500,slideshowStart:"start slideshow",slideshowStop:"stop slideshow",photoRegex:/\.(gif|png|jp(e|g|eg)|bmp|ico|webp|jxr|svg)((#|\?).*)?$/i,retinaImage:!1,retinaUrl:!1,retinaSuffix:"@2x.$1",current:"image {current} of {total}",previous:"previous",next:"next",close:"close",xhrError:"This content failed to load.",imgError:"This image failed to load.",returnFocus:!0,trapFocus:!0,onOpen:!1,onLoad:!1,onComplete:!1,onCleanup:!1,onClosed:!1,rel:function(){return this.rel},href:function(){return t(this).attr("href")},title:function(){return this.title},createImg:function(){var e=new Image,i=t(this).data("cbox-img-attrs");return"object"==typeof i&&t.each(i,function(t,i){e[t]=i}),e},createIframe:function(){var i=e.createElement("iframe"),n=t(this).data("cbox-iframe-attrs");return"object"==typeof n&&t.each(n,function(t,e){i[t]=e}),"frameBorder"in i&&(i.frameBorder=0),"allowTransparency"in i&&(i.allowTransparency="true"),i.name=(new Date).getTime(),i.allowFullscreen=!0,i}},Y="colorbox",Z="cbox",te=Z+"Element",ee=Z+"_open",ie=Z+"_load",ne=Z+"_complete",oe=Z+"_cleanup",re=Z+"_closed",he=Z+"_purge",ae=t("<a/>"),se="div",le=0,de={},ce=function(){function t(){clearTimeout(h)}function e(){(_.get("loop")||W[A+1])&&(t(),h=setTimeout(J.next,_.get("slideshowSpeed")))}function i(){S.html(_.get("slideshowStop")).unbind(s).one(s,n),ae.bind(ne,e).bind(ie,t),x.removeClass(a+"off").addClass(a+"on")}function n(){t(),ae.unbind(ne,e).unbind(ie,t),S.html(_.get("slideshowStart")).unbind(s).one(s,function(){J.next(),i()}),x.removeClass(a+"on").addClass(a+"off")}function o(){r=!1,S.hide(),t(),ae.unbind(ne,e).unbind(ie,t),x.removeClass(a+"off "+a+"on")}var r,h,a=Z+"Slideshow_",s="click."+Z;return function(){r?_.get("slideshow")||(ae.unbind(oe,o),o()):_.get("slideshow")&&W[1]&&(r=!0,ae.one(oe,o),_.get("slideshowAuto")?i():n(),S.show())}}();t[Y]||(t(p),J=t.fn[Y]=t[Y]=function(e,i){var n,o=this;return e=e||{},t.isFunction(o)&&(o=t("<a/>"),e.open=!0),o[0]?(p(),m()&&(i&&(e.onComplete=i),o.each(function(){var i=t.data(this,Y)||{};t.data(this,Y,t.extend(i,e))}).addClass(te),n=new r(o[0],e),n.get("open")&&f(o[0])),o):o},J.position=function(e,i){function n(){T[0].style.width=k[0].style.width=b[0].style.width=parseInt(x[0].style.width,10)-D+"px",b[0].style.height=C[0].style.height=H[0].style.height=parseInt(x[0].style.height,10)-j+"px"}var r,h,s,l=0,d=0,c=x.offset();if(E.unbind("resize."+Z),x.css({top:-9e4,left:-9e4}),h=E.scrollTop(),s=E.scrollLeft(),_.get("fixed")?(c.top-=h,c.left-=s,x.css({position:"fixed"})):(l=h,d=s,x.css({position:"absolute"})),d+=_.get("right")!==!1?Math.max(E.width()-_.w-z-D-a(_.get("right"),"x"),0):_.get("left")!==!1?a(_.get("left"),"x"):Math.round(Math.max(E.width()-_.w-z-D,0)/2),l+=_.get("bottom")!==!1?Math.max(o()-_.h-N-j-a(_.get("bottom"),"y"),0):_.get("top")!==!1?a(_.get("top"),"y"):Math.round(Math.max(o()-_.h-N-j,0)/2),x.css({top:c.top,left:c.left,visibility:"visible"}),y[0].style.width=y[0].style.height="9999px",r={width:_.w+z+D,height:_.h+N+j,top:l,left:d},e){var g=0;t.each(r,function(t){return r[t]!==de[t]?(g=e,void 0):void 0}),e=g}de=r,e||x.css(r),x.dequeue().animate(r,{duration:e||0,complete:function(){n(),$=!1,y[0].style.width=_.w+z+D+"px",y[0].style.height=_.h+N+j+"px",_.get("reposition")&&setTimeout(function(){E.bind("resize."+Z,J.position)},1),t.isFunction(i)&&i()},step:n})},J.resize=function(t){var e;U&&(t=t||{},t.width&&(_.w=a(t.width,"x")-z-D),t.innerWidth&&(_.w=a(t.innerWidth,"x")),I.css({width:_.w}),t.height&&(_.h=a(t.height,"y")-N-j),t.innerHeight&&(_.h=a(t.innerHeight,"y")),t.innerHeight||t.height||(e=I.scrollTop(),I.css({height:"auto"}),_.h=I.height()),I.css({height:_.h}),e&&I.scrollTop(e),J.position("none"===_.get("transition")?0:_.get("speed")))},J.prep=function(i){function o(){return _.w=_.w||I.width(),_.w=_.mw&&_.mw<_.w?_.mw:_.w,_.w}function a(){return _.h=_.h||I.height(),_.h=_.mh&&_.mh<_.h?_.mh:_.h,_.h}if(U){var d,g="none"===_.get("transition")?0:_.get("speed");I.remove(),I=n(se,"LoadedContent").append(i),I.hide().appendTo(M.show()).css({width:o(),overflow:_.get("scrolling")?"auto":"hidden"}).css({height:a()}).prependTo(b),M.hide(),t(q).css({"float":"none"}),c(_.get("className")),d=function(){function i(){t.support.opacity===!1&&x[0].style.removeAttribute("filter")}var n,o,a=W.length;U&&(o=function(){clearTimeout(Q),L.hide(),u(ne),_.get("onComplete")},F.html(_.get("title")).show(),I.show(),a>1?("string"==typeof _.get("current")&&R.html(_.get("current").replace("{current}",A+1).replace("{total}",a)).show(),K[_.get("loop")||a-1>A?"show":"hide"]().html(_.get("next")),P[_.get("loop")||A?"show":"hide"]().html(_.get("previous")),ce(),_.get("preloading")&&t.each([h(-1),h(1)],function(){var i,n=W[this],o=new r(n,t.data(n,Y)),h=o.get("href");h&&s(o,h)&&(h=l(o,h),i=e.createElement("img"),i.src=h)})):O.hide(),_.get("iframe")?(n=_.get("createIframe"),_.get("scrolling")||(n.scrolling="no"),t(n).attr({src:_.get("href"),"class":Z+"Iframe"}).one("load",o).appendTo(I),ae.one(he,function(){n.src="//about:blank"}),_.get("fastIframe")&&t(n).trigger("load")):o(),"fade"===_.get("transition")?x.fadeTo(g,1,i):i())},"fade"===_.get("transition")?x.fadeTo(g,0,function(){J.position(0,d)}):J.position(g,d)}},J.next=function(){!$&&W[1]&&(_.get("loop")||W[A+1])&&(A=h(1),f(W[A]))},J.prev=function(){!$&&W[1]&&(_.get("loop")||A)&&(A=h(-1),f(W[A]))},J.close=function(){U&&!G&&(G=!0,U=!1,u(oe),_.get("onCleanup"),E.unbind("."+Z),v.fadeTo(_.get("fadeOut")||0,0),x.stop().fadeTo(_.get("fadeOut")||0,0,function(){x.hide(),v.hide(),u(he),I.remove(),setTimeout(function(){G=!1,u(re),_.get("onClosed")},1)}))},J.remove=function(){x&&(x.stop(),t[Y].close(),x.stop(!1,!0).remove(),v.remove(),G=!1,x=null,t("."+te).removeData(Y).removeClass(te),t(e).unbind("click."+Z).unbind("keydown."+Z))},J.element=function(){return t(_.el)},J.settings=X)})(jQuery,document,window);
if(typeof naviki == "undefined" || !naviki){	var naviki = {};};naviki.Lang = {	local_lang:{"NavikiException.1000.msg":"Routing server HTTP exception","NavikiException.1001.msg":"Enter target for a routing request","NavikiException.1002.msg":"Routing temporarily not available","NavikiException.3000.msg":"Consider correct spelling. Enter address as follows: Street, Town, Country","NavikiException.5012.msg":"This route is already available in your Naviki app, see 'My Naviki'","NavikiException.5013.msg":"This route is already available on your Naviki app, see 'My Naviki'","NavikiException.5017.msg":"Your app version is outdated. Please install update.","NavikiException.5100.msg":"The GPS file {0} is too large. Maximum size is 5 MB.","NavikiException.5101.msg":"Naviki handles GPX, KML, KMZ and TCX files.","NavikiException.5102.msg":"An error occurred processing {0}. Please try again.","NavikiException.5104.msg":"Your file {0} does not contain a route.","NavikiException.5200":"That's not possible. This team doesn't exist.","NavikiException.5201.msg":"This contest is finished.","NavikiException.5204.msg":"Team modification is not possible any more.","NavikiException.5205.msg":"Sorry, that's impossible","NavikiException.5206.msg":"Possible after you joined the contest","NavikiException.5207.msg":"Participation is already clarified.","NavikiException.5208.msg":"This is not possible since you already joined a team","NavikiException.5209.msg":"Sorry, that's impossible since this team doesn't exist","NavikiException.5210.msg":"This team is already complete","NavikiException.5211.msg":"Coupon code not available for this contest.","NavikiException.5212.msg":"Sorry, that's impossible since this team doesn't exist","NavikiException.5213.msg":"Can't add this route to the contest, because useAllWays is activated","NavikiException.5214.msg":"This participant left the contest","NavikiException.5215.msg":"Sorry, that's impossible since the participant is not connected to the team","NavikiException.5219.msg":"This team name already exists. Insert a different name.","NavikiException.5300.msg":"This coupon code is not valid. Please correct your input.","NavikiException.5301.msg":"This code has been used so often, that it is not valid any longer.","NavikiException.5302.msg":"Sorry, this coupon code is not yet or no longer available.","NavikiException.5705.msg":"An error occurred while calculating the tax for your address. Please make sure your address is correct and try again. If the error persists, click 'Contact us' and send a brief message.","NavikiException.5706.msg":"It's currently not possible to automatically calculate tax in your area. Click 'Contact us' to ask for an invoice.","NavikiException.5707.msg":"We are unable to calculate the tax based on your input. Please make sure you enter as much detail as possible, i.e. state, postal code, etc.","NavikiException.5708.msg":"The Tax-ID you entered seems to be invalid. Please make sure the Tax-ID is correct and try again. If the error persists, click 'Contact us' and send a brief message.","NavikiException.5709.msg":"An error occurred while retrieving the tax for your address. Please reload the page and try again. If the error persists, click 'Contact us' and send a brief message.","SelectPointsOfInterestAccommodation":"Accommodation","SelectPointsOfInterestActivitiesAndExperiences":"Activity / Experience","SelectPointsOfInterestAdministration":"Administration","SelectPointsOfInterestAircraft":"Aircraft","SelectPointsOfInterestApartment":"Apartment / B&B","SelectPointsOfInterestBakery":"Bakery","SelectPointsOfInterestBeachesSwimmingAndAnglingLakes":"Beach / Swimming / Angling lake","SelectPointsOfInterestBedAndBike":"Bed + Bike","SelectPointsOfInterestBedAndBreakfast":"Bed & Breakfast","SelectPointsOfInterestBicycleRental":"Bicycle rental","SelectPointsOfInterestBicycleService":"Bicycle service","SelectPointsOfInterestBicycleTube":"Bicycle tube automat","SelectPointsOfInterestBiergarten":"Beer garden","SelectPointsOfInterestBikeFriend":"Bike Friends","SelectPointsOfInterestBooksMagazines":"Books / Magazines","SelectPointsOfInterestBuildingsAndMonuments":"Building / Monument","SelectPointsOfInterestBus":"Bus","SelectPointsOfInterestBusiness":"Business","SelectPointsOfInterestCableCar":"Cable car","SelectPointsOfInterestCafe":"Café","SelectPointsOfInterestCampSite":"Camp site","SelectPointsOfInterestCamping":"Camping","SelectPointsOfInterestCarRental":"Car rental","SelectPointsOfInterestChargingStation":"Charging Station","SelectPointsOfInterestClothing":"Clothes","SelectPointsOfInterestCompressedAir":"Compressed air","SelectPointsOfInterestCraft":"Craft","SelectPointsOfInterestCulture":"Culture","SelectPointsOfInterestCultureHobby":"Culture / Hobby","SelectPointsOfInterestDoItYourself":"Do-it-yourself","SelectPointsOfInterestDrinkingWater":"Drinking water","SelectPointsOfInterestEducation":"Education","SelectPointsOfInterestElectronics":"Electronics","SelectPointsOfInterestEmergency":"Emergency","SelectPointsOfInterestEntertainment":"Entertainment","SelectPointsOfInterestFastFood":"Fast food","SelectPointsOfInterestFerryRoutes":"Ferry route","SelectPointsOfInterestFinance":"Finances","SelectPointsOfInterestFoodDrink":"Food / Drink","SelectPointsOfInterestForChildren":"For children","SelectPointsOfInterestFuel":"Gas station","SelectPointsOfInterestFunGames":"Fun / Games","SelectPointsOfInterestGarageStore":"Garage / Shop","SelectPointsOfInterestGasStation":"Gas station","SelectPointsOfInterestGrocery":"Grocery","SelectPointsOfInterestGuestHouse":"Guest house / B&B","SelectPointsOfInterestHealth":"Health","SelectPointsOfInterestHealthBeauty":"Health / Beauty","SelectPointsOfInterestHolidayHouseAgencies":"Holiday house agency","SelectPointsOfInterestHolidayResort":"Holiday resort","SelectPointsOfInterestHostel":"Hostel","SelectPointsOfInterestHotel":"Hotel","SelectPointsOfInterestHut":"Hut","SelectPointsOfInterestIceCream":"Ice cream parlour","SelectPointsOfInterestInstitution":"Institution","SelectPointsOfInterestInteriorFurniture":"Interior / Furniture","SelectPointsOfInterestLeisureRecreation":"Recreation","SelectPointsOfInterestLocalSpecialities":"Local specialities","SelectPointsOfInterestMallSupermarket":"Supermarket","SelectPointsOfInterestMoreInfos":"Read more ...","SelectPointsOfInterestMuseumsGalleriesAndCrafts":"Museum / Gallery / Craft","SelectPointsOfInterestNaturalAreasParksAndGardens":"Natural area / Park / Garden","SelectPointsOfInterestNatureCamp":"Nature camp","SelectPointsOfInterestNatureLandscape":"Nature / Landscape","SelectPointsOfInterestOrganisation":"Organisation","SelectPointsOfInterestParking":"Parking facility","SelectPointsOfInterestPicnicSite":"Picnic site","SelectPointsOfInterestPlaygroundsAndOutdoorFitness":"Playground / Outdoor fitness","SelectPointsOfInterestPolice":"Police","SelectPointsOfInterestPostalService":"Postal service","SelectPointsOfInterestPubBar":"Pub / Bar","SelectPointsOfInterestRailway":"Railway","SelectPointsOfInterestReligiousSite":"Religious site","SelectPointsOfInterestRenting":"Rental","SelectPointsOfInterestRepairAndChargingStation":"Repair / Shop","SelectPointsOfInterestRestaurant":"Restaurant","SelectPointsOfInterestShelter":"Shelter","SelectPointsOfInterestShip":"Ship","SelectPointsOfInterestShop":"Shop","SelectPointsOfInterestSports":"Sports","SelectPointsOfInterestSportsOutdoorTravel":"Sports / Outdoor / Travel","SelectPointsOfInterestStationeryGifts":"Stationery / Gifts","SelectPointsOfInterestTaxi":"Taxi","SelectPointsOfInterestThemeParksAquaDomesAndZoos":"Theme park / Aqua dome / Zoo","SelectPointsOfInterestTourismInformation":"Tourism information","SelectPointsOfInterestTradeService":"Trade / Services","SelectPointsOfInterestTraffic":"Transport","SelectPointsOfInterestTransportAndInformation":"Transport / Information","SelectPointsOfInterestVendingMachine":"Vending machine","SelectPointsOfInterestWarningTooManyItems":"Select up to five categories at once.","SelectPointsOfInterestWellness":"Wellness","abc.daysToStart":"days until start <br>of the ABC!","abc.daysToStart.teaser":"All ABC leaderboards<br>at a glance ...","abc.header.kilometers":"km cycled so far in ABC %1$s","base.action":"Actions","base.add":"add","base.apply":"apply","base.attention":"Note","base.back":"back","base.bookmark":"save","base.by":"at","base.cancel":"cancel","base.close":"close","base.cookieChoices":"To provide best user experience we apply cookies.","base.copy":"Copy","base.deactivatedAccountChangeEmailButton":"Change e-mail address","base.deactivatedAccountHint":"Please verify your e-mail address to activate your account. We sent you a confirmation mail to {0}.","base.deactivatedAccountResendButton":"Resend activation mail","base.delete":"delete","base.edit":"edit","base.error":"Fault","base.feet":"ft","base.hour":"h","base.kilometre":"km","base.kilometre.kmh":"km/h","base.language":"Language","base.meter":"m","base.miles":"mi","base.miles.mih":"mi/h","base.more":"Learn more ...","base.move":"move","base.next":"next","base.no":"no","base.notsupported":"This function is not supported by your browser.","base.ok":"OK","base.photos":"Photos","base.pleaseSelect":"Please select","base.pleaseWait":"Please wait","base.print":"print","base.send":"Calculate","base.serviceNotAvailable":"Service not available","base.store":"save","base.switchDirection":"Invert direction","base.yes":"yes","contest.address.label.addressOne":"Address line 1","contest.address.label.addressTwo":"Address line 2","contest.address.label.city":"City","contest.address.label.company":"Company","contest.address.label.country":"Country","contest.address.label.firstname":"First name","contest.address.label.lastname":"Surname","contest.address.label.state":"State","contest.address.label.vat":"Tax-ID","contest.address.label.vatType":"Tax-ID Type","contest.address.label.zip":"Postal code","contest.address.tooltip.vat":"If you purchase as a company, enter your company's Tax-ID. Ignore this field in case you are a private customer.","contest.checkout.address":"Billing address","contest.checkout.checkInfo":"Make sure all details are correct. You can't edit the Contest afterwards. Click the back button to check and edit now.","contest.checkout.description.company":"Click on ‘Contact us’ to email us. Your Contest will be saved as soon as you click ‘Contact us’. We will send an invoice on request and we will be happy to answer any further question.","contest.checkout.description.contactUs.thankYou":"Thanks for your enquiry. We will get in touch with you shortly. Your Contest will be unlocked as soon as the payment has been made.","contest.checkout.description.noPaypal":"No problem. Click 'Contact us' below and send the generated message. We will respond shortly.","contest.checkout.description.nonEu":"As you are not located in the EU, we can't provide the direct checkout. Please click 'Contact us' to send an email. We will then send you an invoice. Your configured Contest will be saved by clicking 'Contact us'.","contest.checkout.description.share":"Spread the word about your Contest now! Copy the following link and send it along your invitation:","contest.checkout.description.thankYou":"Find your contest in the Naviki app under MORE / Contests. Copy the link at the bottom and send it to your participants.","contest.checkout.error.paypal":"An error occurred while processing the PayPal order. The payment will be refunded automatically, in case it was already transferred. Please try it again or click 'Contact us'.","contest.checkout.label.copyLink":"Copy link","contest.checkout.priceSummary":"Summary","contest.checkout.tax.sales":"Sales tax","contest.checkout.tax.vat":"VAT","contest.checkout.title.checkout":"Checkout","contest.checkout.title.noPaypal":"Can't pay with any of these options?","contest.checkout.title.payment":"Payment","contest.checkout.title.thankYou":"Congrats! You successfully set up your Contest!","contest.checkout.total":"Total","contest.chooseArea":"Select a city","contest.chooseTeam":"Select a team","contest.configure.description.categories":"Create categories for an additional dimension. Each participant must then join a category. You can then apply rankings for categories. If you allow team creation (see above), people in a team can come from different categories.","contest.configure.description.groups":"If you create groups here, each participant must join a group. You can then apply group rankings. If you allow team creation (see above), people in a team must come from the same group.","contest.configure.description.logo":"Drop image file here","contest.configure.description.rankings":"Your Contest can contain various rankings. Set them below.","contest.configure.description.template":"Try out the templates below! Then edit everything as you like.","contest.configure.error.categories":"Add at least two categories to apply the categories option","contest.configure.error.groups":"Add at least two groups if you want to apply the groups option","contest.configure.heading":"Set up %s","contest.configure.heading.categories":"Categories","contest.configure.heading.general":"General","contest.configure.heading.groups":"Groups","contest.configure.heading.heatmap":"Heatmap","contest.configure.heading.misc":"Participants and teams","contest.configure.heading.rankings":"Rankings","contest.configure.heading.template":"Templates","contest.configure.imageTooBig":"The image file is too large. Max. file size: 10 MB","contest.configure.label.category-name":"Category name","contest.configure.label.category-type":"Category type","contest.configure.label.contest-distance-notification":"App sends nudges","contest.configure.label.description":"Intro text","contest.configure.label.end-date":"End date","contest.configure.label.global-heatmap":"Global heatmap enabled","contest.configure.label.group-name":"Group name","contest.configure.label.link":"Your website linked from the Contest","contest.configure.label.logo":"Contest image","contest.configure.label.max-member":"Maximum participants","contest.configure.label.password":"Code word","contest.configure.label.personal-heatmap":"Personal heatmaps enabled","contest.configure.label.ranking-in":"In?","contest.configure.label.ranking-what":"What?","contest.configure.label.ranking-who":"Who?","contest.configure.label.start-date":"Start date","contest.configure.label.team-heatmap":"Team heatmaps enabled","contest.configure.label.team-message-available":"Team members can chat","contest.configure.label.teams-available":"Participants create teams","contest.configure.label.title":"Contest title","contest.configure.tooltip.contest-distance-notification":"If this is enabled, participants will get a push message each time they have recorded a good distance.","contest.configure.tooltip.description":"Briefly explain why and for whom you're hosting the Contest. Mention if you have prizes and what for. The description is visible to all app users.","contest.configure.tooltip.global-heatmap":"All participants can see a heatmap including all trips cycled by all participants.","contest.configure.tooltip.group-password":"Participants have to enter this password when they want to join this group. Leave this field empty if you don't want to apply a password for this group.","contest.configure.tooltip.link":"Your website should include an option to contact you.","contest.configure.tooltip.logo":"Drop an image or logo here. Consider that small details and small text will not be readable.","contest.configure.tooltip.max-member":"Maximum number of people who can join your Contest. If you leave this field empty, the limit of your package applies.","contest.configure.tooltip.password":"Participants have to enter this word to join your Contest. Keep it simple and use just one word. Leave this field empty if you don't want to apply a code.","contest.configure.tooltip.personal-heatmap":"Each participant has access to a personal heatmap on which all their recorded trips are shown.","contest.configure.tooltip.team-heatmap":"Each participant can see a heatmap of all trips recorded by their team.","contest.configure.tooltip.team-message-available":"If this is enabled, team members can chat within their team.","contest.configure.tooltip.teams-available":"If this is enabled, participants can create and join teams.","contest.configure.tooltip.title":"Give your Contest a short and catchy name. It will be visible to all app users.","contest.confirmation.email.body":"<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\"><title></title></head><body text=\"#000000\" bgcolor=\"#FFFFFF\"><p>Hello %s,</p><p>Thank you for ordering a Naviki Contest. Have fun cycling and competing!</p><p>We will send you an invoice and further information shortly.</p><p>Kind regards,<br>Naviki</p><p>--<br>Naviki Team<br>c/o beemo GmbH<br><a href=\"mailto:naviki@naviki.org\">naviki@naviki.org</a><br/><a href=\"http://www.naviki.org/\">www.naviki.org</a><br><!-- IMAGE-PLACEHOLDER --></p></body></html>","contest.confirmation.email.subject":"Your Naviki Contest: Confirmation","contest.contactUs":"Contact us","contest.contactUs.body":"Hello Naviki-Team,\n\nI would like to order a Naviki Contest.\nPlease send an invoice to:\n###FIRSTNAME### ###LASTNAME###\n###ADDRESS###\nKind regards\n###FIRSTNAME### ###LASTNAME###\n\nPlease do not delete or edit the following paragraph as it is required to identify your contest.\n=======\nUser-ID: ###UID###\nContest-ID: ###CID###\nContest-Title: ###CTITLE###\nContest-SKU: ###SKU###","contest.contactUs.subject":"Naviki Contest order request","contest.deletedUser":"Deleted user","contest.global":"Contest","contest.invoice.email.body":"<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\"><title></title></head><body text=\"#000000\" bgcolor=\"#FFFFFF\"><p>Hello %s,<br/>Thank you for ordering a Naviki Contest. Enjoy cycling with your target group!<br>Please find your invoice attached.</p><p>Naviki</p><p>--<br>Naviki Team<br>c/o beemo GmbH<br><a href=\"mailto:naviki@naviki.org\">naviki@naviki.org</a><br/><a href=\"http://www.naviki.org/\">www.naviki.org</a><br><!-- IMAGE-PLACEHOLDER --></p></body></html>","contest.invoice.email.subject":"Your Naviki Contest: invoice","contest.joinContest":"Participate","contest.joinContestHint":"Join Contest now!","contest.joinTeam":"Join team","contest.joinTeamHint":"Join a team now!","contest.leaveArea":"Leave city","contest.leaveContest":"Leave Contest","contest.leaveContestHint":"You're in the Contest","contest.leaveTeam":"Leave team","contest.leaveTeamHint":"You're part of this team","contest.linkToRegistration":"See and change team or sign up now ...","contest.manage.downloadRankings":"Download rankings","contest.manage.member.ban":"Ban member","contest.manage.member.banned":"Banned","contest.manage.member.description":"Ban or unban members of your contest. Banned members will show up in rankings with 0 km.","contest.manage.member.search":"Search person","contest.manage.member.unban":"Unban member","contest.member":"Single","contest.memberCategory":"Category","contest.memberNotFound":"Person not found","contest.message.confirmJoinningToTeam.message":"The team captain confirmed. Your trips now also count for the team.","contest.message.confirmJoinningToTeam.title":"You successfully joined the team","contest.message.email.regards":"Naviki","contest.message.email.salutation":"Hello %s,","contest.message.email.teammessage.hint":"Please contact your team in the Naviki app. Don't reply to this message.","contest.message.email.teammessage.subject":"New team message from %s","contest.message.joinToTeam.message":"Someone wants to join your team. Go to your team to confirm.","contest.message.joinToTeam.title":"Team member request","contest.message.leaveFromTeam.message":"Free position in your team. Invite someone to join!","contest.message.leaveFromTeam.title":"Someone left your team","contest.myCity":"My city","contest.myTeam":"My team","contest.nextStep":"Next step","contest.participate.loginHint":"%1$s Log in at Naviki %2$s to participate!","contest.participate.welcomeAppMessage":"You're ready for the Contest. Get the Naviki app and record cycled trips!","contest.product.buyNow":"Set up now","contest.product.description.contest-l":"The versatile splendid challenge","contest.product.description.contest-m":"Perfect for organisations with sub-units","contest.product.description.contest-s":"Ideal for friends and small organisations","contest.product.description.custom":"Customised package if you want even more","contest.product.feature.acceptConditions":"I accept the","contest.product.feature.acceptConditionsHint":"Please accept the Terms of Service","contest.product.feature.ariaLabelNo":"%s is not included in %s","contest.product.feature.ariaLabelYes":"%s is included in %s","contest.product.feature.conditions":"Terms of Service","contest.product.feature.everythingFromBefore":"Everything from %s","contest.product.feature.isBoundaryAllowed":"Spatial boundaries","contest.product.feature.isContestHeatmapAllowed":"Contest-wide heatmap","contest.product.feature.isControlQuestionForTopLevelAllowed":"Specific password for each group","contest.product.feature.isCustomRulesAllowed":"Custom rules","contest.product.feature.isCustomWishesAllowed":"Custom features","contest.product.feature.isInfrastructureMessagesAllowed":"Infrastructure reports","contest.product.feature.isMemberCategoryAllowed":"Categories for participants","contest.product.feature.isPersonalHeatmapAllowed":"Personal heatmaps","contest.product.feature.isPushMessagingAllowed":"Push messages","contest.product.feature.isTeamAllowed":"Teams","contest.product.feature.isTeamCategoryAllowed":"Team categories","contest.product.feature.isTeamHeatmapAllowed":"Team heatmaps","contest.product.feature.isTeamMessageAllowed":"Team chat","contest.product.feature.isTopLevelAllowed":"Groups","contest.product.feature.maxDuration":"max. %s days","contest.product.feature.maxParticipants":"max. %s participants","contest.product.feature.price":"Price","contest.product.feature.priceInfo":"incl. VAT","contest.product.feature.unlimitedDuration":"Unlimited duration","contest.product.feature.unlimitedParticipants":"Unlimited participants","contest.product.title.contest-l":"Contest L","contest.product.title.contest-m":"Contest M","contest.product.title.contest-s":"Contest S","contest.product.title.custom":"Premium Contest","contest.ranking.absolute":"Total km","contest.ranking.relative":"Average km of members","contest.searchArea":"Search city ...","contest.searchTeam":"Search your team ...","contest.show.hint":"Get the app to take part in the Contest:","contest.team":"Team","contest.teamCategory":"Team category","contest.timePeriod.hint":"Active period finished","contest.timePeriod.proportional":"Active period finished. Achieved km appear in the ranking proportionately on a daily basis.","contest.topLevel":"Group","contest.university.employees":"Employees %s","contest.university.founding":"Founded %s","contest.university.students":"Students %s","error.contest.payment.generic":"Something went wrong while processing your payment. Please try again later. If this error persists, click to 'Contact us' and send a brief message.","error.generic":"Something went wrong. Please try again later.","error.routeNotAllowed.msg":"The owner must change the visibility of this route so that it can be seen here.","error.routeNotAllowed.title":"Restricted visibility","error.routeNotFound.msg":"Route with given ID not found. The creator of the route should verify it exists.","error.routeNotFound.title":"Route not found","error.wayUidDeprecated.msg":"The URL must contain the UUID of a route.","error.wayUidDeprecated.title":"Invalid parameter","pi_map.bicycle_node_title":"Bicycle node %s","pi_map.contextMenu_centerOnThisLocation":"Center on this location","pi_map.earthTour":"GoogleEarth","pi_map.enlarge_picture":"Enlarge picture ...","pi_map.error.web_gl_disabled.msg":"Naviki could not load the map (Mapbox GL). Maybe your browser does not support WebGL or it is disabled. Please make sure that your web browser is up to date and WebGL is enabled or try another browser.","pi_map.error.web_gl_disabled.title":"Could not load map","pi_map.fullscreen_max":"Maximise map","pi_map.fullscreen_min":"Minimise map","pi_map.geolocation":"Display own position","pi_map.geolocation.missing_permission":"Authorise Naviki to use your location in browser settings.","pi_map.landingpage_layer.body":"Learn more on Naviki Page","pi_map.landingpage_layer.title":"%s: More info on cycling","pi_map.more":"more...","pi_map.privacy_policy":"Privacy","pi_map.stops_layer_from":"Information on trip from here","pi_map.stops_layer_to":"Information on trip to here","pi_map.terms_of_use":"Terms of use","pi_map.type.3d":"Muscle Ache","pi_map.type.3d_bicycle":"Muscle Ache Plus","pi_map.type.alternative":"Chain Oil","pi_map.type.alternative_bicycle":"Chain Oil Plus","pi_map.type.default":"Tailwind","pi_map.type.default_bicycle":"Tailwind Plus","pi_map.type.enable_bike_highlighting":"Highlight official bike routes and numbered nodes","pi_map.type.enable_localized_labels":"Translate place names","pi_map.type.google.satellite":"Satellite","pi_map.type.osm":"OpenStreetMap","pi_map.type.osm_bike":"OSM Bike","pi_map_open_app.button":"Start Naviki App","pi_portlet.allWays.button.filter.calculated":"Calculated","pi_portlet.allWays.button.filter.others":"Other source","pi_portlet.allWays.button.filter.recorded":"Recorded","pi_portlet.allWays.button.sort":"Sort ...","pi_portlet.allWays.button.sort.dateLastChange":"Date of last change","pi_portlet.allWays.button.sort.dateOrigin":"Date of creation","pi_portlet.allWays.button.sort.length":"Length","pi_portlet.allWays.footer":"This is the space for your calculated, recorded and imported routes","pi_portlet.myWays":"My own routes","pi_portlet.myWaysFooter":"This space is for routes you recorded with your Naviki app","pi_portlet.wayBookmarks":"My saved routes","pi_portlet.wayBookmarksFooter":"This space is for routes you marked as \"saved\"","pi_routing_request.add_destination":"Route destination","pi_routing_request.add_start":"Route from here","pi_routing_request.add_target":"Add destination","pi_routing_request.attention.address_not_found":"Please consider correct spelling and enter addresses as follows: Street, Town, Country","pi_routing_request.attention.empty_inputfields":"Please fill in all boxes!","pi_routing_request.attention.no_route_between_points":"Naviki cannot calculate any route between these locations. Please try again with alternative locations.","pi_routing_request.attention.routing_not_possible":"Naviki temporarily cannot calculate any route. Please try again later.","pi_routing_request.close_to_roundtrip":"Round trip","pi_routing_request.current_location":"Display own position","pi_routing_request.default":"Street, no., place","pi_routing_request.destination":"Destination","pi_routing_request.imbed":"imbed","pi_routing_request.start":"Start","pi_routing_request.submit":"Calculate","pi_routing_widget.header":"Calculate cycle route","pi_routing_widget.start":"Start","pi_routing_widget.target":"Destination","pi_search":"Search","pi_search.hint":"Location or search term","pi_search.invalid":"Please don't enter special characters (*+:;%$Â§...)!","pi_search.no_result":"No result found for search term","pi_search.to_short":"Please enter a longer search term!","pi_userprofile":"Naviki user profile","pi_userprofile.co2.engineType":"Consumption type","pi_userprofile.co2.gasconsumption":"average consumption per 100 km","pi_userprofile.co2.gasconsumption_not_valid":"Please insert a realistic value ​​between 3 and 20 litres.","pi_userprofile.co2.grams_per_kilometer":"g/km","pi_userprofile.co2.header":"My car","pi_userprofile.co2.individual_co2_per_km":"CO2 emission","pi_userprofile.co2.individual_co2_per_km_not_valid":"Insert a realistic value.","pi_userprofile.co2.liter":"litre","pi_userprofile.co2.vehicleClass":"Vehicle","pi_userprofile.delete":"Delete profile ...","pi_userprofile.deleteDialog":"Are you sure, you want to delete your profile including all routes?","pi_userprofile.disable":"Deactivate account","pi_userprofile.disableDialog":"Are you sure, you want to disable your profile? Your data will be deleted after 30 days. Within this time you can reactivate your account via login.","pi_userprofile.email":"E-mail","pi_userprofile.email_already_exists":"This e-mail address is already being used for a Naviki account.","pi_userprofile.email_change_hint":"We have sent you a confirmation email. Click the link in that email. Afterwards your updated email address will appear here.","pi_userprofile.email_not_valid":"This e-mail address is invalid.","pi_userprofile.energy.kilocalories":"kcal","pi_userprofile.energy.kilojoule":"kJ","pi_userprofile.engine.diesel":"diesel","pi_userprofile.engine.gasoline":"gasoline","pi_userprofile.engine.naturalgas":"natural gas","pi_userprofile.general":"General","pi_userprofile.image":"Image","pi_userprofile.logoutDialog":"Really want to log out?","pi_userprofile.mybike.header":"My bicycle","pi_userprofile.mybike.type":"Bicycle type","pi_userprofile.mybike.type.bike_without_drive_support":"Ordinary bicycle","pi_userprofile.mybike.type.e_bike":"E-bike","pi_userprofile.mybike.type.s_pedelec":"S-pedelec","pi_userprofile.name":"Name","pi_userprofile.newsletter":"Naviki newsletter by e-mail","pi_userprofile.notifications.header":"Notifications","pi_userprofile.personal.age":"Age","pi_userprofile.personal.average_speed":"Average speed","pi_userprofile.personal.average_speed.sub":"Enter the average speed at which you cycle without obstacles. When you plan routes, Naviki considers this speed as well as the influence of traffic lights and gradients, to predict the average speed on these routes.","pi_userprofile.personal.average_speed_not_valid":"Please insert a realistic speed.","pi_userprofile.personal.birthday":"Birthday","pi_userprofile.personal.birthday_not_valid":"Please insert a realistic age between 2 and 110 years.","pi_userprofile.personal.gender":"Gender","pi_userprofile.personal.gender.diverse":"diverse","pi_userprofile.personal.gender.female":"female","pi_userprofile.personal.gender.male":"male","pi_userprofile.personal.header":"Personal","pi_userprofile.personal.size":"Body size","pi_userprofile.personal.size_not_valid":"Please insert a realistic size ​​between 80 and 240 cm.","pi_userprofile.personal.weight":"Body weight","pi_userprofile.personal.weight_not_valid":"Please insert a realistic value ​​between 10 and 180 kg.","pi_userprofile.privacy.header":"Privacy","pi_userprofile.privacy.profile":"I agree that other Naviki users can see information such as my profile picture and my user name.","pi_userprofile.privacy.profile.sub":"If enabled, other Naviki users can see your nickname and profile image.","pi_userprofile.privacy.profile.title":"Profile","pi_userprofile.privacy.ways":"I agree that other Naviki users can find my routes.","pi_userprofile.privacy.ways.button.all.private":"Make all routes visible only for me","pi_userprofile.privacy.ways.button.all.private.confirm":"Really set the visibility of all routes to 'Only me'? Afterwards, even routes that have already been shared via a link or widget will no longer be visible to others.","pi_userprofile.privacy.ways.button.all.public":"Make all routes visible for anyone","pi_userprofile.privacy.ways.button.all.public.confirm":"Do you really want all routes to be visible for 'Anyone'?","pi_userprofile.privacy.ways.button.all.shared":"Make all routes visible for people with the link","pi_userprofile.privacy.ways.button.all.shared.confirm":"Do you really want to set the visibility of all routes to 'People with the link' now?","pi_userprofile.privacy.ways.button.all.success":"All routes updated successfully","pi_userprofile.privacy.ways.options.title":"Who can see my new routes?","pi_userprofile.privacy.ways.sub":"Use the buttons to set who can see your future recorded or saved routes. Use the following links to adjust the visibility of your existing routes.","pi_userprofile.privacy.ways.title":"Visibility of routes","pi_userprofile.units.energy":"Energy","pi_userprofile.units.energy.kilocalories":"Kilocalories (kcal)","pi_userprofile.units.energy.kilojoules":"Kilojoules (kJ)","pi_userprofile.units.header":"Units","pi_userprofile.units.length":"Distance","pi_userprofile.units.length.kilometers":"Metric (kilometers)","pi_userprofile.units.length.miles":"Imperial (miles)","pi_userprofile.vehicle.costs":"Car costs","pi_userprofile.vehicle.costs.unit":"Cent/km","pi_userprofile.vehicle.individual":"individual input","pi_userprofile.vehicle.middle_class":"Medium class car","pi_userprofile.vehicle.middle_class_diesel":"Medium class (diesel 174 g CO2/km)","pi_userprofile.vehicle.middle_class_gasoline":"Medium class (gasoline 216 g CO2/km)","pi_userprofile.vehicle.not_available":"I don't have a car","pi_userprofile.vehicle.small_car":"Small car","pi_userprofile.vehicle.small_car_diesel":"Small car (diesel 125 g CO2/km)","pi_userprofile.vehicle.small_car_gasoline":"Small car (gasoline 160 g CO2/km)","pi_userprofile.vehicle.sub":"With this information, Naviki calculates your cost savings and CO2 avoidance for each route. Based on your recorded trips, you can see in the Naviki app under My Activities how much money and CO2 you save by cycling over time.","pi_userprofile.vehicle.upper_class":"Upper class car","pi_userprofile.vehicle.upper_class_diesel":"Upper class (diesel 229 g CO2/km)","pi_userprofile.vehicle.upper_class_gasoline":"Upper class (gasoline 271 g CO2/km)","pi_way.additional_options":"Special characteristic","pi_way.additional_options.premium_way":"Quality-assured Premium Route","pi_way.crdate":"Trip cycled at","pi_way.delete_path_question":"Really want to delete this route?","pi_way.description":"Description","pi_way.env":"Surroundings","pi_way.env_city":"Town/city","pi_way.env_forest":"Woodland","pi_way.env_hills":"Low mountain ranges","pi_way.env_industry":"Industry","pi_way.env_meadow":"Meadows/fields","pi_way.env_mountains":"High mountain ranges","pi_way.env_river":"River/lake","pi_way.env_sea":"Dyke/coast","pi_way.env_village":"Countryside/villages","pi_way.middle_traffic_flow":"Medium traffic volume","pi_way.middle_traffic_flow_very_high":"very high","pi_way.middle_traffic_flow_very_low":"very low","pi_way.private_way":"Who can see this route?","pi_way.private_way.private":"Only me","pi_way.private_way.public":"Anyone","pi_way.private_way.shared":"People with the link","pi_way.recorded":"How was the route created?","pi_way.recorded.other":"Other source","pi_way.recorded.self":"Cycled and recorded by myself","pi_way.special_usage":"Suitability","pi_way.special_usage_children":"Children","pi_way.special_usage_m_t_b":"MTB","pi_way.special_usage_racing":"Racer","pi_way.special_usage_seniors":"Senior citizens","pi_way.special_usage_sparetime":"Leisure","pi_way.special_usage_tourism":"Tourism","pi_way.title":"Title","pi_way_info.bookmark_path_name":"Name","pi_way_info.calories_burned":"Calories burned","pi_way_info.co2_background_info":"Background information","pi_way_info.co2_savings":"CO2 avoidance","pi_way_info.download_fit_text":"(e.g. Garmin)","pi_way_info.download_gpx_text":"(e.g. Garmin, Magellan)","pi_way_info.download_header":"GPS files","pi_way_info.download_kml_paths_text":"(as paths, e.g. for editing in Google Earth)","pi_way_info.download_kml_text":"(e.g. Google Earth)","pi_way_info.download_ovl_text":"(e.g. Top50 maps)","pi_way_info.download_tcx_text":"(e.g. Garmin Training Center)","pi_way_info.heightProfileLabelX":"Length in","pi_way_info.heightProfileLabelY":"Height in","pi_way_info.infoheightdiff":"Height in meters upwards","pi_way_info.infoheightmax":"Highest point","pi_way_info.infoheightmin":"Lowest point","pi_way_info.infokm":"Length","pi_way_info.infokmtime":"Ride time at %s km/h","pi_way_info.menu_bookmark":"Save","pi_way_info.menu_delete":"Delete","pi_way_info.menu_download":"GPS download","pi_way_info.menu_edit":"Edit","pi_way_info.menu_print":"Print view","pi_way_info.menu_rating":"Rate","pi_way_info.menu_report":"Report","pi_way_info.no_description":"No description available yet.","pi_way_info.no_images":"No image available yet.","pi_way_info.ownerprofile_private":"The user doesn't provide this information publicly.","pi_way_info.price_savings":"Car costs","pi_way_info.print_info":"Naviki is creating your print view. This may take a while, depending on the length of the route.","pi_way_info.rating":"Rating","pi_way_info.rating.1":"very bad","pi_way_info.rating.2":"bad","pi_way_info.rating.3":"medium","pi_way_info.rating.4":"good","pi_way_info.rating.5":"very good","pi_way_info.rating_header":"Route rating","pi_way_info.report_header":"Route report","pi_way_info.starting_points_title":"Starting points","pi_way_info.tab_description":"Info","pi_way_info.tab_energy":"Energy","pi_way_info.tab_images":"Illustrations","pi_way_info.tab_overview":"Overview","pi_way_info.tab_owner":"Owner","pi_way_info.tab_profile":"Heights","pi_way_info.unsuited":"Reports","pi_way_info.unsuitedforbicycles_1":"You have reported this route as impassable","pi_way_info.unsuitedforbicycles_2":"Report this route as impassable","pi_way_info.user_profile":"More routes from %s ...","pi_way_info.widget_more":"More routes on","pi_way_preview.toMapview":"To mapview ...","tx_naviki_domain_model_bicyclemarkingappointment":"Appointment","tx_naviki_domain_model_bicyclemarkingappointment.captcha_response":"Please insert the word shown in the image.","tx_naviki_domain_model_bicyclemarkingappointment.city":"Place of residence","tx_naviki_domain_model_bicyclemarkingappointment.color":"Color","tx_naviki_domain_model_bicyclemarkingappointment.date_of_birth":"Date of birth","tx_naviki_domain_model_bicyclemarkingappointment.email":"E-mail","tx_naviki_domain_model_bicyclemarkingappointment.forename":"Name","tx_naviki_domain_model_bicyclemarkingappointment.framenumber":"Number","tx_naviki_domain_model_bicyclemarkingappointment.gearing":"Gearing","tx_naviki_domain_model_bicyclemarkingappointment.manufacturer":"Type","tx_naviki_domain_model_bicyclemarkingappointment.notes":"Special characteristics","tx_naviki_domain_model_bicyclemarkingappointment.start":"Appointment","tx_naviki_domain_model_bicyclemarkingappointment.steering":"Handlebar","tx_naviki_domain_model_bicyclemarkingappointment.street":"Street / No.","tx_naviki_domain_model_bicyclemarkingappointment.surname":"Name","tx_naviki_domain_model_bicyclemarkingappointment.zip":"Postcode","tx_naviki_domain_model_bicyclemarkingevent":"Bicycle event","tx_naviki_domain_model_bicyclemarkingevent.name":"Name","tx_naviki_domain_model_bicyclemarkingevent.periods":"Periods","tx_naviki_domain_model_bicyclemarkingevent.time_slot_length":"Time slot","tx_naviki_domain_model_bicyclemarkingperiod":"Period","tx_naviki_domain_model_bicyclemarkingperiod.appointments":"Appointments","tx_naviki_domain_model_bicyclemarkingperiod.end":"End","tx_naviki_domain_model_bicyclemarkingperiod.start":"Start","tx_naviki_pibicyclemarkingappointment.calendar.awarded":"occupied","tx_naviki_pibicyclemarkingappointment.calendar.free":"free","tx_naviki_pibicyclemarkingappointment.calendar.weekdays":"Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday","tx_naviki_pibicyclemarkingappointment.choose_date":"Select date","tx_naviki_pibicyclemarkingappointment.dialog":"Would you like to register now for the bicycle coding?","tx_naviki_pibicyclemarkingappointment.dialog.back":"Back to the form","tx_naviki_pibicyclemarkingappointment.dialog.submit":"Sign in","tx_naviki_pibicyclemarkingappointment.dialog.title":"Registration","tx_naviki_pibicyclemarkingappointment.error.1221559976":"This email address is invalid.","tx_naviki_pibicyclemarkingappointment.error.1221560718":"Please fill in the following field","tx_naviki_pibicyclemarkingappointment.error.1221563685":"Only digits (0-9) are allowed.","tx_naviki_pibicyclemarkingappointment.error.1238108067":"Please enter your zip code from five digits.","tx_naviki_pibicyclemarkingappointment.error.1238108069":"Maximum of 256 characters.","tx_naviki_pibicyclemarkingappointment.error.1398857830":"The appointment is already booked.","tx_naviki_pibicyclemarkingappointment.error.1398857831":"You are already registered for this appointment.","tx_naviki_pibicyclemarkingappointment.my_bike":"My bicycle","tx_naviki_pibicyclemarkingappointment.personal_information":"Personal information","tx_naviki_pibicyclemarkingappointment.required_fields":"Fields marked by a star (*) have to be filled","tx_naviki_pibicyclemarkingappointment.submit":"Log in","tx_naviki_pimap.imprint":"Legal notice (Naviki)","tx_naviki_pimapcontrol.bookmark.btn":"Save route and use it on App","tx_naviki_pimapcontrol.bookmark.btn.active":"Saved","tx_naviki_pimapcontrol.bookmark.dlg.success":"This route is now available in your Naviki app, see 'My Naviki'","tx_naviki_pimapcontrol.bookmark.hint.officialRoute":"Titles of Premium and Official Routes can't be edited","tx_naviki_pimapcontrol.bookmark.tip":"Use in Naviki app","tx_naviki_pimapcontrol.calculated.tip":"This route is calculated by Naviki","tx_naviki_pimapcontrol.download.tip":"Download GPS file","tx_naviki_pimapcontrol.edit.tip":"Edit route","tx_naviki_pimapcontrol.embed.dlg.code":"Embed route in your website","tx_naviki_pimapcontrol.embed.dlg.type.div":"div (recommended)","tx_naviki_pimapcontrol.embed.dlg.type.iframe":"iframe","tx_naviki_pimapcontrol.embed.tip":"Embed on website","tx_naviki_pimapcontrol.official.tip":"Officially designated cycle route","tx_naviki_pimapcontrol.premium.tip":"Quality-assured Premium Route","tx_naviki_pimapcontrol.profile.cadence":"Cadence [1/min]","tx_naviki_pimapcontrol.profile.duration":"Duration [h]","tx_naviki_pimapcontrol.profile.height":"Height [m]","tx_naviki_pimapcontrol.profile.height.imperial":"Height [ft]","tx_naviki_pimapcontrol.profile.length":"Length [km]","tx_naviki_pimapcontrol.profile.length.imperial":"Length [mi]","tx_naviki_pimapcontrol.profile.pulse":"Pulse [bpm]","tx_naviki_pimapcontrol.profile.speed":"Speed [km/h]","tx_naviki_pimapcontrol.profile.speed.imperial":"Speed [mi/h]","tx_naviki_pimapcontrol.recorded.tip":"This is a recorded trip","tx_naviki_pimapcontrol.search.list.header":"Routes of","tx_naviki_pimapcontrol.search.placeholder":"City, landscape, river, ...","tx_naviki_pimapcontrol.search.tip":"Finds routes of other users and official routes","tx_naviki_pimapcontrol.share.dlg.heading":"Copy link to share this route","tx_naviki_pimapcontrol.share.tip":"Share link to this route","tx_naviki_piwidget.widget.bookmark.btn":"Use route with Naviki"	},	getLL:function(key){		if(key in naviki.Lang.local_lang){			return naviki.Lang.local_lang[key];		}		return key;	}};
/*
 * jQuery Nivo Slider v3.2
 * http://nivo.dev7studios.com
 *
 * Copyright 2012, Dev7studios
 * Free to use and abuse under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 */

(function(e){var t=function(t,n){var r=e.extend({},e.fn.nivoSlider.defaults,n);var i={currentSlide:0,currentImage:"",totalSlides:0,running:false,paused:false,stop:false,controlNavEl:false};var s=e(t);s.data("nivo:vars",i).addClass("nivoSlider");var o=s.children();o.each(function(){var t=e(this);var n="";if(!t.is("img")){if(t.is("a")){t.addClass("nivo-imageLink");n=t}t=t.find("img:first")}var r=r===0?t.attr("width"):t.width(),s=s===0?t.attr("height"):t.height();if(n!==""){n.css("display","none")}t.css("display","none");i.totalSlides++});if(r.randomStart){r.startSlide=Math.floor(Math.random()*i.totalSlides)}if(r.startSlide>0){if(r.startSlide>=i.totalSlides){r.startSlide=i.totalSlides-1}i.currentSlide=r.startSlide}if(e(o[i.currentSlide]).is("img")){i.currentImage=e(o[i.currentSlide])}else{i.currentImage=e(o[i.currentSlide]).find("img:first")}if(e(o[i.currentSlide]).is("a")){e(o[i.currentSlide]).css("display","block")}var u=e("<img/>").addClass("nivo-main-image");u.attr("src",i.currentImage.attr("src")).show();s.append(u);e(window).resize(function(){s.children("img").width(s.width());u.attr("src",i.currentImage.attr("src"));u.stop().height("auto");e(".nivo-slice").remove();e(".nivo-box").remove()});s.append(e('<div class="nivo-caption"></div>'));var a=function(t){var n=e(".nivo-caption",s);if(i.currentImage.attr("title")!=""&&i.currentImage.attr("title")!=undefined){var r=i.currentImage.attr("title");if(r.substr(0,1)=="#")r=e(r).html();if(n.css("display")=="block"){setTimeout(function(){n.html(r)},t.animSpeed)}else{n.html(r);n.stop().fadeIn(t.animSpeed)}}else{n.stop().fadeOut(t.animSpeed)}};a(r);var f=0;if(!r.manualAdvance&&o.length>1){f=setInterval(function(){d(s,o,r,false)},r.pauseTime)}if(r.directionNav){s.append('<div class="nivo-directionNav"><a class="nivo-prevNav">'+r.prevText+'</a><a class="nivo-nextNav">'+r.nextText+"</a></div>");e(s).on("click","a.nivo-prevNav",function(){if(i.running){return false}clearInterval(f);f="";i.currentSlide-=2;d(s,o,r,"prev")});e(s).on("click","a.nivo-nextNav",function(){if(i.running){return false}clearInterval(f);f="";d(s,o,r,"next")})}if(r.controlNav){i.controlNavEl=e('<div class="nivo-controlNav"></div>');s.after(i.controlNavEl);for(var l=0;l<o.length;l++){if(r.controlNavThumbs){i.controlNavEl.addClass("nivo-thumbs-enabled");var c=o.eq(l);if(!c.is("img")){c=c.find("img:first")}if(c.attr("data-thumb"))i.controlNavEl.append('<a class="nivo-control" rel="'+l+'"><img src="'+c.attr("data-thumb")+'" alt="" /></a>')}else{i.controlNavEl.append('<a class="nivo-control" rel="'+l+'">'+(l+1)+"</a>")}}e("a:eq("+i.currentSlide+")",i.controlNavEl).addClass("active");e("a",i.controlNavEl).bind("click",function(){if(i.running)return false;if(e(this).hasClass("active"))return false;clearInterval(f);f="";u.attr("src",i.currentImage.attr("src"));i.currentSlide=e(this).attr("rel")-1;d(s,o,r,"control")})}if(r.pauseOnHover){s.hover(function(){i.paused=true;clearInterval(f);f=""},function(){i.paused=false;if(f===""&&!r.manualAdvance){f=setInterval(function(){d(s,o,r,false)},r.pauseTime)}})}s.bind("nivo:animFinished",function(){u.attr("src",i.currentImage.attr("src"));i.running=false;e(o).each(function(){if(e(this).is("a")){e(this).css("display","none")}});if(e(o[i.currentSlide]).is("a")){e(o[i.currentSlide]).css("display","block")}if(f===""&&!i.paused&&!r.manualAdvance){f=setInterval(function(){d(s,o,r,false)},r.pauseTime)}r.afterChange.call(this)});var h=function(t,n,r){if(e(r.currentImage).parent().is("a"))e(r.currentImage).parent().css("display","block");e('img[src="'+r.currentImage.attr("src")+'"]',t).not(".nivo-main-image,.nivo-control img").width(t.width()).css("visibility","hidden").show();var i=e('img[src="'+r.currentImage.attr("src")+'"]',t).not(".nivo-main-image,.nivo-control img").parent().is("a")?e('img[src="'+r.currentImage.attr("src")+'"]',t).not(".nivo-main-image,.nivo-control img").parent().height():e('img[src="'+r.currentImage.attr("src")+'"]',t).not(".nivo-main-image,.nivo-control img").height();for(var s=0;s<n.slices;s++){var o=Math.round(t.width()/n.slices);if(s===n.slices-1){t.append(e('<div class="nivo-slice" name="'+s+'"><img src="'+r.currentImage.attr("src")+'" style="position:absolute; width:'+t.width()+"px; height:auto; display:block !important; top:0; left:-"+(o+s*o-o)+'px;" /></div>').css({left:o*s+"px",width:t.width()-o*s+"px",height:i+"px",opacity:"0",overflow:"hidden"}))}else{t.append(e('<div class="nivo-slice" name="'+s+'"><img src="'+r.currentImage.attr("src")+'" style="position:absolute; width:'+t.width()+"px; height:auto; display:block !important; top:0; left:-"+(o+s*o-o)+'px;" /></div>').css({left:o*s+"px",width:o+"px",height:i+"px",opacity:"0",overflow:"hidden"}))}}e(".nivo-slice",t).height(i);u.stop().animate({height:e(r.currentImage).height()},n.animSpeed)};var p=function(t,n,r){if(e(r.currentImage).parent().is("a"))e(r.currentImage).parent().css("display","block");e('img[src="'+r.currentImage.attr("src")+'"]',t).not(".nivo-main-image,.nivo-control img").width(t.width()).css("visibility","hidden").show();var i=Math.round(t.width()/n.boxCols),s=Math.round(e('img[src="'+r.currentImage.attr("src")+'"]',t).not(".nivo-main-image,.nivo-control img").height()/n.boxRows);for(var o=0;o<n.boxRows;o++){for(var a=0;a<n.boxCols;a++){if(a===n.boxCols-1){t.append(e('<div class="nivo-box" name="'+a+'" rel="'+o+'"><img src="'+r.currentImage.attr("src")+'" style="position:absolute; width:'+t.width()+"px; height:auto; display:block; top:-"+s*o+"px; left:-"+i*a+'px;" /></div>').css({opacity:0,left:i*a+"px",top:s*o+"px",width:t.width()-i*a+"px"}));e('.nivo-box[name="'+a+'"]',t).height(e('.nivo-box[name="'+a+'"] img',t).height()+"px")}else{t.append(e('<div class="nivo-box" name="'+a+'" rel="'+o+'"><img src="'+r.currentImage.attr("src")+'" style="position:absolute; width:'+t.width()+"px; height:auto; display:block; top:-"+s*o+"px; left:-"+i*a+'px;" /></div>').css({opacity:0,left:i*a+"px",top:s*o+"px",width:i+"px"}));e('.nivo-box[name="'+a+'"]',t).height(e('.nivo-box[name="'+a+'"] img',t).height()+"px")}}}u.stop().animate({height:e(r.currentImage).height()},n.animSpeed)};var d=function(t,n,r,i){var s=t.data("nivo:vars");if(s&&s.currentSlide===s.totalSlides-1){r.lastSlide.call(this)}if((!s||s.stop)&&!i){return false}r.beforeChange.call(this);if(!i){u.attr("src",s.currentImage.attr("src"))}else{if(i==="prev"){u.attr("src",s.currentImage.attr("src"))}if(i==="next"){u.attr("src",s.currentImage.attr("src"))}}s.currentSlide++;if(s.currentSlide===s.totalSlides){s.currentSlide=0;r.slideshowEnd.call(this)}if(s.currentSlide<0){s.currentSlide=s.totalSlides-1}if(e(n[s.currentSlide]).is("img")){s.currentImage=e(n[s.currentSlide])}else{s.currentImage=e(n[s.currentSlide]).find("img:first")}if(r.controlNav){e("a",s.controlNavEl).removeClass("active");e("a:eq("+s.currentSlide+")",s.controlNavEl).addClass("active")}a(r);e(".nivo-slice",t).remove();e(".nivo-box",t).remove();var o=r.effect,f="";if(r.effect==="random"){f=new Array("sliceDownRight","sliceDownLeft","sliceUpRight","sliceUpLeft","sliceUpDown","sliceUpDownLeft","fold","fade","boxRandom","boxRain","boxRainReverse","boxRainGrow","boxRainGrowReverse");o=f[Math.floor(Math.random()*(f.length+1))];if(o===undefined){o="fade"}}if(r.effect.indexOf(",")!==-1){f=r.effect.split(",");o=f[Math.floor(Math.random()*f.length)];if(o===undefined){o="fade"}}if(s.currentImage.attr("data-transition")){o=s.currentImage.attr("data-transition")}s.running=true;var l=0,c=0,d="",m="",g="",y="";if(o==="sliceDown"||o==="sliceDownRight"||o==="sliceDownLeft"){h(t,r,s);l=0;c=0;d=e(".nivo-slice",t);if(o==="sliceDownLeft"){d=e(".nivo-slice",t)._reverse()}d.each(function(){var n=e(this);n.css({top:"0px"});if(c===r.slices-1){setTimeout(function(){n.animate({opacity:"1.0"},r.animSpeed,"",function(){t.trigger("nivo:animFinished")})},100+l)}else{setTimeout(function(){n.animate({opacity:"1.0"},r.animSpeed)},100+l)}l+=50;c++})}else if(o==="sliceUp"||o==="sliceUpRight"||o==="sliceUpLeft"){h(t,r,s);l=0;c=0;d=e(".nivo-slice",t);if(o==="sliceUpLeft"){d=e(".nivo-slice",t)._reverse()}d.each(function(){var n=e(this);n.css({bottom:"0px"});if(c===r.slices-1){setTimeout(function(){n.animate({opacity:"1.0"},r.animSpeed,"",function(){t.trigger("nivo:animFinished")})},100+l)}else{setTimeout(function(){n.animate({opacity:"1.0"},r.animSpeed)},100+l)}l+=50;c++})}else if(o==="sliceUpDown"||o==="sliceUpDownRight"||o==="sliceUpDownLeft"){h(t,r,s);l=0;c=0;var b=0;d=e(".nivo-slice",t);if(o==="sliceUpDownLeft"){d=e(".nivo-slice",t)._reverse()}d.each(function(){var n=e(this);if(c===0){n.css("top","0px");c++}else{n.css("bottom","0px");c=0}if(b===r.slices-1){setTimeout(function(){n.animate({opacity:"1.0"},r.animSpeed,"",function(){t.trigger("nivo:animFinished")})},100+l)}else{setTimeout(function(){n.animate({opacity:"1.0"},r.animSpeed)},100+l)}l+=50;b++})}else if(o==="fold"){h(t,r,s);l=0;c=0;e(".nivo-slice",t).each(function(){var n=e(this);var i=n.width();n.css({top:"0px",width:"0px"});if(c===r.slices-1){setTimeout(function(){n.animate({width:i,opacity:"1.0"},r.animSpeed,"",function(){t.trigger("nivo:animFinished")})},100+l)}else{setTimeout(function(){n.animate({width:i,opacity:"1.0"},r.animSpeed)},100+l)}l+=50;c++})}else if(o==="fade"){h(t,r,s);m=e(".nivo-slice:first",t);m.css({width:t.width()+"px"});m.animate({opacity:"1.0"},r.animSpeed*2,"",function(){t.trigger("nivo:animFinished")})}else if(o==="slideInRight"){h(t,r,s);m=e(".nivo-slice:first",t);m.css({width:"0px",opacity:"1"});m.animate({width:t.width()+"px"},r.animSpeed*2,"",function(){t.trigger("nivo:animFinished")})}else if(o==="slideInLeft"){h(t,r,s);m=e(".nivo-slice:first",t);m.css({width:"0px",opacity:"1",left:"",right:"0px"});m.animate({width:t.width()+"px"},r.animSpeed*2,"",function(){m.css({left:"0px",right:""});t.trigger("nivo:animFinished")})}else if(o==="boxRandom"){p(t,r,s);g=r.boxCols*r.boxRows;c=0;l=0;y=v(e(".nivo-box",t));y.each(function(){var n=e(this);if(c===g-1){setTimeout(function(){n.animate({opacity:"1"},r.animSpeed,"",function(){t.trigger("nivo:animFinished")})},100+l)}else{setTimeout(function(){n.animate({opacity:"1"},r.animSpeed)},100+l)}l+=20;c++})}else if(o==="boxRain"||o==="boxRainReverse"||o==="boxRainGrow"||o==="boxRainGrowReverse"){p(t,r,s);g=r.boxCols*r.boxRows;c=0;l=0;var w=0;var E=0;var S=[];S[w]=[];y=e(".nivo-box",t);if(o==="boxRainReverse"||o==="boxRainGrowReverse"){y=e(".nivo-box",t)._reverse()}y.each(function(){S[w][E]=e(this);E++;if(E===r.boxCols){w++;E=0;S[w]=[]}});for(var x=0;x<r.boxCols*2;x++){var T=x;for(var N=0;N<r.boxRows;N++){if(T>=0&&T<r.boxCols){(function(n,i,s,u,a){var f=e(S[n][i]);var l=f.width();var c=f.height();if(o==="boxRainGrow"||o==="boxRainGrowReverse"){f.width(0).height(0)}if(u===a-1){setTimeout(function(){f.animate({opacity:"1",width:l,height:c},r.animSpeed/1.3,"",function(){t.trigger("nivo:animFinished")})},100+s)}else{setTimeout(function(){f.animate({opacity:"1",width:l,height:c},r.animSpeed/1.3)},100+s)}})(N,T,l,c,g);c++}T--}l+=100}}};var v=function(e){for(var t,n,r=e.length;r;t=parseInt(Math.random()*r,10),n=e[--r],e[r]=e[t],e[t]=n);return e};var m=function(e){if(this.console&&typeof console.log!=="undefined"){console.log(e)}};this.stop=function(){if(!e(t).data("nivo:vars").stop){e(t).data("nivo:vars").stop=true;m("Stop Slider")}};this.start=function(){if(e(t).data("nivo:vars").stop){e(t).data("nivo:vars").stop=false;m("Start Slider")}};r.afterLoad.call(this);return this};e.fn.nivoSlider=function(n){return this.each(function(r,i){var s=e(this);if(s.data("nivoslider")){return s.data("nivoslider")}var o=new t(this,n);s.data("nivoslider",o)})};e.fn.nivoSlider.defaults={effect:"random",slices:15,boxCols:8,boxRows:4,animSpeed:500,pauseTime:3e3,startSlide:0,directionNav:true,controlNav:true,controlNavThumbs:false,pauseOnHover:true,manualAdvance:false,prevText:"Prev",nextText:"Next",randomStart:false,beforeChange:function(){},afterChange:function(){},slideshowEnd:function(){},lastSlide:function(){},afterLoad:function(){}};e.fn._reverse=[].reverse})(jQuery);
/**
 * 
 */
;(function($) {
	$( document ).ready(function() {

		// for each teaser-box: add link from header also to content text
		$("#teaser .teaser-box").each(function() {
			const teaserBox = $(this);
			const href = teaserBox.find("header a:first").attr("href");
			if (href) {
				const aTag = $('<a>',{ href: href });
				teaserBox.find("div.ce-textpic:first").wrap(aTag);
			}
		});
		
		$.fn.navikiStartPage = function() {
			$("#teaser").stop();
			
			if($( window ).width() > 991) {
				
				var margin = 20;
				var current = margin - $( document ).scrollTop();
				var lowerLimit = $( window ).height() - ($("#stage").outerHeight() + $("#stage").offset().top) + margin;
				var upperLimit = $( window ).height() - ($(".nivo-caption").first().outerHeight() + $(".nivo-caption").first().offset().top + $("#teaser").outerHeight());
	
				var bottom = current;
				if(current <= lowerLimit){
					bottom = lowerLimit;
				} else if(current >= upperLimit){
					bottom = upperLimit;
				}
				
				$("#teaser").animate({
				    bottom: bottom
				  },1200, function() {
				    // Animation complete.
				});
			
			} else {
				
				if ($("#teaser").css("bottom") != "") {
					$("#teaser").css("bottom","");
				}
				if ($("#teaser").css("position") != "") {
					$("#teaser").css("position","");
				}
			}
		};	
		
		var findCaption = window.setInterval(function(){
			
			if($(".nivo-caption")){
				
				if($(".nivo-caption").first().height() == null) {
					return;
				}
				
				if($(".nivo-caption").first().offset() == null){
					return;
				}

				$( ".nivo-caption" ).wrap( "<div class='container'></div>" );
				$( ".nivo-caption" ).wrap( "<div class='row'></div>" );
				$( ".nivo-caption" ).addClass("col-md-6 col-lg-3");

				$.fn.navikiStartPage();
				window.clearInterval(findCaption);
			
				$( ".nivo-caption" ).animate({
					opacity: 1.0
				  },2000, function() {
				    // Animation complete.
				});
				
				$( window ).resize(function() {
					$.fn.navikiStartPage();
				});
				
				$( window ).scroll(function () {
					$.fn.navikiStartPage();
				});
			}
		}, 1000);
		
		var findControlNav = window.setInterval(function(){
			if($(".nivo-controlNav").length > 0){
				$( ".nivo-controlNav" ).wrap( "<div class='container'></div>" );
				$( ".nivo-controlNav" ).wrap( "<div class='row'></div>" );
				$( ".nivo-controlNav" ).addClass("col-md-6 col-lg-3");
				$( ".nivo-controlNav" ).animate({
					opacity: 1.0
				  },2000, function() {
				    // Animation complete.
				});
				window.clearInterval(findControlNav);
			}
		}, 1000);	
	});
	
})( jQuery);
/**
 * Call function if user clicked on naviki logo. Changes the window location to
 * the start of the landingpage.
 * 
 */
;(function($) {
	
	$.fn.navikiFullPage = function() {
		
		/*
		 * Setup bootstrap column definitions for content elements (all other layouts)
		 */
		$('#fullpage .container:not(.layout-10) .ce-textpic.ce-intext.ce-right .ce-gallery').addClass('col-lg-6');
		$('#fullpage .container:not(.layout-10) .ce-textpic.ce-intext.ce-right .ce-bodytext').addClass('col-lg-5 col-lg-offset-1');
		$('#fullpage .container:not(.layout-10) .ce-textpic.ce-intext.ce-left .ce-gallery').addClass('col-lg-5 col-lg-offset-1');
		$('#fullpage .container:not(.layout-10) .ce-textpic.ce-intext.ce-left .ce-bodytext').addClass('col-lg-5');
		$('#fullpage .container:not(.layout-10) .ce-textpic.ce-intext.ce-right.ce-nowrap .ce-gallery').addClass('col-lg-6');
		$('#fullpage .container:not(.layout-10) .ce-textpic.ce-intext.ce-right.ce-nowrap .ce-bodytext').addClass('col-lg-5 col-lg-offset-1');
		$('#fullpage .container:not(.layout-10) .ce-textpic.ce-intext.ce-left.ce-nowrap .ce-gallery').addClass('col-lg-5 col-lg-offset-1');
		$('#fullpage .container:not(.layout-10) .ce-textpic.ce-intext.ce-left.ce-nowrap .ce-bodytext').addClass('col-lg-5');

		/* layout-10 (plain container) gets more dynamic grid boxes */
		$('#fullpage .container.layout-10 .ce-textpic.ce-intext.ce-right .ce-gallery').addClass('col-lg-6');
		$('#fullpage .container.layout-10 .ce-textpic.ce-intext.ce-right .ce-bodytext').addClass('col-lg col-lg-offset-1');
		$('#fullpage .container.layout-10 .ce-textpic.ce-intext.ce-left .ce-gallery').addClass('col-lg col-lg-offset-1');
		$('#fullpage .container.layout-10 .ce-textpic.ce-intext.ce-left .ce-bodytext').addClass('col-lg');
		$('#fullpage .container.layout-10 .ce-textpic.ce-intext.ce-right.ce-nowrap .ce-gallery').addClass('col-lg-6');
		$('#fullpage .container.layout-10 .ce-textpic.ce-intext.ce-right.ce-nowrap .ce-bodytext').addClass('col-lg col-lg-offset-1');
		$('#fullpage .container.layout-10 .ce-textpic.ce-intext.ce-left.ce-nowrap .ce-gallery').addClass('col-lg col-lg-offset-1');
		$('#fullpage .container.layout-10 .ce-textpic.ce-intext.ce-left.ce-nowrap .ce-bodytext').addClass('col-lg');
	
		/*
		 * Setup full-page classes, only first level of section items
		 */
		const sectionItems = $('#fullpage > .section');
		const length = sectionItems.length;
		sectionItems.each(function(index) {		
			var container = $(this).find(".container").first();
			
			if ($(this).hasClass("wrap")) {
				$(this).addClass("section-table");	
				container.wrap( "<div class='section-table-cell'></div>" );
			}
			
			if (index < length - 1) {
				var arrow = "<div class='arrow-down-container'><span data-next='"+(index+1)+"' class='icon-arrow-down-fullscreen'></span></div>";
				container.append(arrow);
			}
			
			if (index == 0) {
				if($(".fixedHeader").length > 0) {
					$(this).addClass("first-section");
				}
			}
		});

		// two-box-layout (layout-8)
		$("#fullpage .layout-8 h4").each(function( index ) {
			const header = $(this);
			const content = header.next();
			header.add(content).wrapAll( "<div class='two-box-layout' />");
		});
		$("#fullpage .layout-8 div.two-box-layout:nth-child(odd)").addClass("two-box-odd");
		$("#fullpage .layout-8 div.two-box-layout:nth-child(even)").addClass("two-box-even");
		$("#fullpage .layout-8 div.two-box-layout").last().after("<div class='two-box-layout-clear' />");
		$("#fullpage .layout-8 div.two-box-layout").wrapAll("<div class='two-box-layout-wrapper' />");

		/*
		 * Scroll effects
		 */
		$("#fullpage .icon-arrow-down-fullscreen").bind('click',function(event){
			const next = $(this).data("next");
			var destination = 0;

			// add additional top offset depending on page content
			const fixedHeaderHeight = $("#fixedHeader").length > 0 ? $("#fixedHeader").outerHeight() : 0;
			const fullPageContainerTop = $('#fullpage').position().top;
			const topOffset = fullPageContainerTop - fixedHeaderHeight;
			destination += topOffset;
			
			$('#fullpage .section').each(function(index) {
				if (index < next) {
					destination += $(this).outerHeight();
				}
			});
			
			if (typeof document.body.style.transitionProperty === 'string') {
			    // CSS3 transitions are supported. Yay!
				const avail = $(document).height() - $(window).height();
				var scroll = 0;

				if (avail > 0) {
					scroll = destination;
          
					if (scroll > avail) {
						scroll = avail;
					}
				} else {
					scroll = 0;
				}

				const deltaScroll = $(window).scrollTop() - scroll;

				// if we don't have to scroll because we're already at the right scrolling level,
				if (!deltaScroll) {
					return; // do nothing
				}
				
				event.preventDefault();

				const scrollContainer = $("#fullpage-scrolling").length > 0 ? $("#fullpage-scrolling") : $("#fullpage");

			    // translate3d forces hardware acceleration, second param is 'y'
			    const translate3d = 'translate3d(0, ' + deltaScroll + 'px, 0)';
				scrollContainer.css({
                	'transition' : ".7s",
                    '-webkit-transform': translate3d,
                    '-moz-transform': translate3d,
                    '-ms-transform':translate3d,
                    'transform': translate3d
                });
				$("#teaser").hide(); // if exists, hide teaser boxes while scrolling (start page)
			    
			    // revert back to native scrollTop
				scrollContainer.bind('transitionend webkitTransitionEnd msTransitionEnd oTransitionEnd', function(event) {
			    	$(document).scrollTop(destination);   	
			    	$(this).removeAttr("style");
			    	$(this).unbind(event);
					$("#teaser").show(); // if exists, show teaser boxes again (start page)
			    });
              
			} else {
				
				try {
					$(document).stop(true, true) // stop potential other jQuery animation (assuming we're the only one doing it)
					$(document).animate({
				        scrollTop: destination
				    }, 1000);
				} catch (e) {
					$(document).scrollTop(destination);   
				}
			}
		});
		
		$(window).on("scroll",function() {
			clearTimeout($.data(this, 'scrollTimer'));
		    $.data(this, 'scrollTimer', setTimeout(function() {
		    	var offset = 0;
				if ($(".fixedHeader").length > 0) {
					offset += $("#fixedHeader").outerHeight();
				}
				$("#fullpage .icon-arrow-down-fullscreen").each(function(index){
					if ($(window).scrollTop() + offset > $(this).offset().top) {
						$(this).css("visibility","hidden");
					} else {
						$(this).css("visibility","visible");
					}
				});
		    }, 250));
		    
			
		});
		
		
		/* NAV-7506
		 * Add break for column layout in smaller (mobile) views. This break is visible under 1199px.
		 */
		const calcCeGallery = function(element) {
			const ceInnerElement = element.find('.ce-outer > .ce-inner').length > 0
				? element.find('.ce-outer > .ce-inner')		// center mode
				: element									// right or left mode

			// clear previous modifications
			ceInnerElement.find('.ce-row.ce-mobile').each(function() { $(this).remove(); });
			ceInnerElement.find('.ce-row.ce-desktop').each(function() { $(this).removeClass("ce-desktop"); });

			// calc width of origin (unmodified) content
			const galleryWidth = element.width();
			const contentWidth = ceInnerElement.width();

			// when content is as wide as container, it seems to be too big and it would break, build specific mobile view
			if (contentWidth === galleryWidth) {
				const mobileRowElement = $("<div class='ce-row ce-mobile'></div>");
				ceInnerElement.append(mobileRowElement);

				// hide current rows for mobile (add css class)
				ceInnerElement.find('.ce-row:not(.ce-mobile)').each(function() { $(this).addClass("ce-desktop"); });

				// copy all row column elements into a separate row for a mobile view that is independent of column count
				ceInnerElement.find('.ce-column').each(function() {
					const elementCopy = $(this).clone(false);
					mobileRowElement.append(elementCopy);
				});
			}
		}
		const calcCeGalleryElements = function() {
			$('#fullpage .ce-image .ce-gallery').each(function() {
				calcCeGallery($(this));
			});
		}
		calcCeGalleryElements();

		/*
		 * toggle the background color between green and white
		 */
		$('#fullpage >.section').each(function(index) {
			if (index % 2 == 0) {
				$(this).css("background-color","#EEEEEE");
			}
		});
		
		/*
		 * pick images from content element and set the image to the background of
		 * the fullpage section
		 */
		const bgImgsIdentifier = '#fullpage .section .csc-default.csc-img-bg img, '
								+ '#fullpage .section .csc-frame-default.csc-img-bg img, '
								+ '#fullpage .section .frame-default.csc-img-bg img';
		const bgImgs = $(bgImgsIdentifier);
		$.each(bgImgs, function(i, bgImg) {
			// get the image-url
			const imageUrl = 'url(' + bgImg.src + ')';
			const parentContainer = $(bgImg).parents('.section');
	
			// traverse along all parents and pick the section element
			parentContainer.css({
				'background-image' : imageUrl,
				'background-repeat': 'no-repeat'
			});
			
			// set full height for section with background image
			parentContainer.addClass("full-height");

			// hide the origin image
			$(bgImg).hide();
		});
		
		/*
		 * pick content element with layout-5 and set 
		 * vertical-align = top
		 */
		const ceIdentifier = '#fullpage .section .csc-default.layout-5, '
							+ '#fullpage .section .csc-frame-default.layout-5, '
							+ '#fullpage .section .frame-default.layout-5';
		const items = $(ceIdentifier);
		$.each(items, function(i, item) {
			// traverse along all parents and pick the section element
			$(item).parents('.section-table-cell').css({
				'vertical-align' : 'top'
			});
		});
		
		/*
		 * for single section, we set the height to full
		 */
		if ($('#fullpage .section').length == 1) {
			$('#fullpage .section').addClass("full-height");
		}

		// NAV-6801
		const calcBgImageHeight = function(element) {
			// get dimension of background image, remove url("")
			const bgImgUrl = element.css('background-image').replace(/url\("|"\)$/ig, "");
			if (bgImgUrl == "" || bgImgUrl == "none") {
				return;
			}

			const bgImg = new Image();
			bgImg.onload = function() {
				const xFactor = $(window).width() / bgImg.width;
				const bgImageHeight = bgImg.height * xFactor;
				// min-height with display:table not working in firefox
				element.css("height", bgImageHeight);
			}
			bgImg.src = bgImgUrl;
		}
		const calcBgImageHeightElements = function() {
			$(".full-height").each(function() {
				calcBgImageHeight($(this));
			});
		}
		calcBgImageHeightElements();


		// re-run all calculations that needs a refresh after a window size change
		var resizeTimeout = null;
		$(window).resize(function() {
			clearTimeout(resizeTimeout);
			resizeTimeout = setTimeout(function() {
				calcCeGalleryElements();
				calcBgImageHeightElements();
			}, 250);
		});
	};
	
	$.fn.navikiFullPage();
	
})( jQuery);