{
    "name": "https://zeemy.com",
    "short_name": "https://zeemy.com",
    "start_url": "https://zeemy.com",
    "display": "fullscreen",
    "background_color": "#ffffff",
    "theme_color": "#000000",
    "icons": [
        {
            "src": "https://zeemy.com/media/images/favicon/1756749187205.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "https://zeemy.com/media/images/favicon/1756749187205.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ]
}<script>
(function(){

	function labelFor(field){

		var holder = field.closest(".form-floating") || field.parentNode;
		var label  = holder ? holder.querySelector("label") : null;

		if(label && label.textContent.trim() !== ""){
			return label.textContent.replace("*", "").replace(/\(optional\)/i, "").trim();
		}

		// nothing to read, so the field name is tidied up instead
		var name = (field.getAttribute("name") || "This field").replace(/\[\]$/, "");

		return name.charAt(0).toUpperCase() + name.slice(1).replace(/[_-]+/g, " ");
	}

	function clearError(field){

		field.classList.remove("is-invalid");

		var holder = field.parentNode;
		var box    = holder ? holder.querySelector(".twh-field-error") : null;

		if(box){ box.parentNode.removeChild(box); }
	}

	function showError(field, message){

		field.classList.add("is-invalid");

		var holder = field.parentNode;

		if(!holder || holder.querySelector(".twh-field-error")){ return; }

		var box = document.createElement("div");
		box.className = "twh-field-error";
		/* the company stylesheet carries this, but signup and login do not load
		   it - so the look is set here as well */
		box.style.color = "#dc3545";
		box.style.fontSize = "13px";
		box.style.marginTop = "4px";
		box.textContent = message;

		holder.appendChild(box);
	}

	function isEmpty(field){

		if(field.type === "checkbox" || field.type === "radio"){
			return !field.checked;
		}

		if(field.type === "file"){
			return !field.files || field.files.length === 0;
		}

		return field.value.trim() === "";
	}

	function check(form){

		var fields = form.querySelectorAll("[required]");
		var first  = null;

		for(var i = 0; i < fields.length; i++){

			var field = fields[i];

			/* something hidden cannot be filled in, so it is not held against
			   anyone - a step that is not on screen yet, for instance */
			if(field.offsetParent === null && field.type !== "hidden"){ continue; }
			if(field.disabled){ continue; }

			if(isEmpty(field)){

				showError(field, labelFor(field) + " is required");

				if(!first){ first = field; }

			}else{
				clearError(field);
			}
		}

		if(first){
			try {
				first.scrollIntoView({ behavior: "smooth", block: "center" });
				first.focus({ preventScroll: true });
			} catch(e) {}
		}

		return (first === null);
	}

	function ready(){

		var forms = document.querySelectorAll("form");

		forms.forEach(function(form){

			if(form.querySelectorAll("[required]").length === 0){ return; }

			// the browser's own bubble would otherwise cover this
			form.setAttribute("novalidate", "novalidate");

			form.addEventListener("submit", function(e){
				if(!check(form)){
					e.preventDefault();
					e.stopPropagation();
				}
			});

			// the message goes as soon as they start filling it in
			form.addEventListener("input", function(e){
				if(e.target && e.target.hasAttribute("required") && !isEmpty(e.target)){
					clearError(e.target);
				}
			});

			form.addEventListener("change", function(e){
				if(e.target && e.target.hasAttribute("required") && !isEmpty(e.target)){
					clearError(e.target);
				}
			});
		});
	}

	if(document.readyState === "loading"){
		document.addEventListener("DOMContentLoaded", ready);
	}else{
		ready();
	}

})();
</script>
