/* Zettai Systems — main site scripts */ (function () { "use strict"; const ROOT = typeof window.SITE_ROOT === "string" ? window.SITE_ROOT : ""; const PAGE_RAW = document.body?.dataset?.page || ""; const PAGE = PAGE_RAW === "service" ? "services" : PAGE_RAW; const NAV_ITEMS = [ { href: "", label: "Home", id: "home" }, { href: "about", label: "About Us", id: "about" }, { href: "services", label: "Services", id: "services" }, { href: "solutions", label: "Solutions", id: "solutions" }, { href: "industries", label: "Industries", id: "industries" }, { href: "careers", label: "Careers", id: "careers" }, { href: "contact", label: "Contact", id: "contact" }, ]; function asset(path) { const clean = String(path || "").replace(/^\//, ""); if (!ROOT) return "/" + clean; return ROOT + clean; } function pageHref(path) { if (!path) return "/"; const clean = String(path).replace(/^\//, ""); if (!ROOT) return "/" + clean; return ROOT + clean; } function navLink(item) { const active = PAGE === item.id ? " active" : ""; return `
  • ${item.label}
  • `; } function renderHeader() { const mount = document.getElementById("site-header"); if (!mount) return; mount.innerHTML = ` `; } function renderFooter() { const mount = document.getElementById("site-footer"); if (!mount) return; mount.innerHTML = ` `; } function renderChromeExtras() { if (document.getElementById("floatingCta")) return; const frag = document.createElement("div"); frag.innerHTML = ` Let's Talk Talk `; document.body.append(...frag.children); } /* Theme */ function getPreferredTheme() { const saved = localStorage.getItem("zs-theme"); if (saved === "light" || saved === "dark") return saved; return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } function applyFavicon(theme) { const link = document.querySelector('link[rel="icon"]'); if (!link) return; const file = theme === "dark" ? "favicon-dark.png" : "favicon.png"; link.setAttribute("type", "image/png"); link.setAttribute("href", asset("assets/logos/" + file)); } function applyTheme(theme) { document.documentElement.setAttribute("data-theme", theme); localStorage.setItem("zs-theme", theme); applyFavicon(theme); const toggle = document.getElementById("themeToggle"); if (toggle) { toggle.setAttribute("aria-label", theme === "dark" ? "Switch to light theme" : "Switch to dark theme"); } } function initTheme() { applyTheme(getPreferredTheme()); document.getElementById("themeToggle")?.addEventListener("click", () => { const next = document.documentElement.getAttribute("data-theme") === "dark" ? "light" : "dark"; applyTheme(next); }); } /* Mobile nav */ function initMobileNav() { const toggle = document.getElementById("menuToggle"); const drawer = document.getElementById("mobileDrawer"); if (!toggle || !drawer) return; const focusableSelector = "a[href], button:not([disabled])"; const close = () => { const wasOpen = drawer.classList.contains("is-open"); drawer.classList.remove("is-open"); drawer.hidden = true; toggle.setAttribute("aria-expanded", "false"); toggle.setAttribute("aria-label", "Open menu"); document.body.classList.remove("nav-open"); const icon = toggle.querySelector("i"); if (icon) icon.className = "bi bi-list"; icon && (icon.style.fontSize = "1.4rem"); if (wasOpen) toggle.focus(); }; const open = () => { drawer.hidden = false; requestAnimationFrame(() => drawer.classList.add("is-open")); toggle.setAttribute("aria-expanded", "true"); toggle.setAttribute("aria-label", "Close menu"); document.body.classList.add("nav-open"); const icon = toggle.querySelector("i"); if (icon) icon.className = "bi bi-x-lg"; icon && (icon.style.fontSize = "1.25rem"); const first = drawer.querySelector(focusableSelector); first?.focus(); }; const focusables = () => { const items = [toggle, ...drawer.querySelectorAll(focusableSelector)]; return items.filter((el) => el && !el.hasAttribute("hidden")); }; toggle.addEventListener("click", () => { if (drawer.classList.contains("is-open")) close(); else open(); }); drawer.querySelectorAll("a").forEach((a) => a.addEventListener("click", close)); window.addEventListener("resize", () => { if (window.innerWidth >= 992) close(); }); document.addEventListener("keydown", (e) => { if (!drawer.classList.contains("is-open")) return; if (e.key === "Escape") { e.preventDefault(); close(); return; } if (e.key !== "Tab") return; const items = focusables(); if (!items.length) return; const first = items[0]; const last = items[items.length - 1]; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } }); } /* Scroll effects */ function initScrollUI() { const header = document.getElementById("headerBar"); const back = document.getElementById("backToTop"); const onScroll = () => { const y = window.scrollY || 0; header?.classList.toggle("is-scrolled", y > 12); back?.classList.toggle("is-visible", y > 480); }; onScroll(); window.addEventListener("scroll", onScroll, { passive: true }); back?.addEventListener("click", () => window.scrollTo({ top: 0, behavior: "smooth" })); } /* Forms */ function validateEmail(value) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); } function validatePhone(value) { const digits = value.replace(/\D/g, ""); return digits.length >= 10; } function formEndpoint(kind) { if (kind === "careers") return "/api/careers.php"; return "/api/contact.php"; } function successMessage(kind) { if (kind === "careers") { return "Thank you. Your application has been sent. Our team will review it and follow up if there is a fit."; } return "Thank you. Your message has been sent. We will get back to you soon."; } function resetSubmitButton(submitBtn) { submitBtn?.classList.remove("is-loading"); if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = submitBtn.dataset.label || "Send"; } } /** Hosting bot-gate (humans_XXXX cookie) returns 409 HTML that fetch cannot execute. */ function applyHostBotCookie(html) { if (!html || typeof html !== "string") return false; const match = html.match(/document\.cookie\s*=\s*["']([^"';]+)["']/i); if (!match) return false; let cookie = match[1].trim(); if (!/;\s*path=/i.test(cookie)) cookie += "; path=/"; document.cookie = cookie; return true; } async function parseFormResponse(response) { const raw = await response.text(); let data = null; try { data = JSON.parse(raw); } catch (_) { data = null; } return { raw, data }; } async function postForm(endpoint, formData, retried) { const response = await fetch(endpoint, { method: "POST", body: formData, headers: { Accept: "application/json" }, credentials: "same-origin", }); const { raw, data } = await parseFormResponse(response); // Bot challenge: set cookie and retry once if (response.status === 409 && !retried && applyHostBotCookie(raw)) { return postForm(endpoint, formData, true); } return { response, data, raw }; } function friendlyFormError(response, data, raw) { if (data?.error) return data.error; if (response.status === 403) { return "Security check failed. Please refresh the page and try again."; } if (response.status === 429) { return "Too many submissions. Please wait a few minutes and try again."; } if (response.status === 409 || (raw && /humans_\d+/i.test(raw))) { return "Security check blocked this send. Please refresh the page and try again, or email solutions@zettaisystems.com."; } if (response.status >= 500) { return "Server error while sending. Please try again later or email us directly."; } if (response.status === 404) { return "Mail service is not available on the server yet. Please email us directly."; } return "Something went wrong while sending. Please try again or email us directly."; } function csrfToken() { const match = document.cookie.match(/(?:^|; )zs_csrf=([^;]*)/); if (match && match[1]) return decodeURIComponent(match[1]); const bytes = new Uint8Array(16); crypto.getRandomValues(bytes); const token = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(""); document.cookie = "zs_csrf=" + token + "; path=/; SameSite=Lax"; return token; } function initForms() { csrfToken(); document.querySelectorAll("[data-form]").forEach((form) => { form.addEventListener("submit", async (e) => { e.preventDefault(); let valid = true; form.querySelectorAll("[data-required]").forEach((field) => { const wrap = field.closest(".form-field") || field.parentElement; const value = (field.value || "").trim(); let ok = value.length > 0; if (field.type === "email") ok = validateEmail(value); if (field.dataset.validate === "phone" && value) ok = validatePhone(value); if (field.type === "checkbox") ok = field.checked; if (field.type === "file") ok = field.files && field.files.length > 0; wrap.classList.toggle("field-invalid", !ok); if (!ok) valid = false; }); const status = form.querySelector(".form-status"); const submitBtn = form.querySelector('[type="submit"]'); const kind = form.getAttribute("data-form") || "contact"; if (!valid) { if (status) { status.className = "form-status is-error"; status.textContent = "Please check the highlighted fields and try again."; } return; } submitBtn?.classList.add("is-loading"); if (submitBtn) { submitBtn.disabled = true; submitBtn.dataset.label = submitBtn.innerHTML; submitBtn.innerHTML = "Sending…"; } if (status) { status.className = "form-status"; status.textContent = ""; } try { const formData = new FormData(form); formData.set("csrf_token", csrfToken()); const { response, data, raw } = await postForm( formEndpoint(kind), formData, false ); resetSubmitButton(submitBtn); if (!response.ok || !data?.ok) { if (status) { status.className = "form-status is-error"; status.textContent = friendlyFormError(response, data, raw); } return; } if (status) { status.className = "form-status is-success"; status.textContent = data.message || successMessage(kind); } if (typeof window.gtag === "function") { window.gtag("event", "generate_lead", { event_category: "form", event_label: kind, }); } form.reset(); form.querySelectorAll(".field-invalid").forEach((el) => el.classList.remove("field-invalid")); } catch (_) { resetSubmitButton(submitBtn); if (status) { status.className = "form-status is-error"; status.textContent = "Network error. Please check your connection or email us directly."; } } }); form.querySelectorAll("input, textarea, select").forEach((field) => { field.addEventListener("input", () => { const wrap = field.closest(".form-field") || field.parentElement; wrap.classList.remove("field-invalid"); }); }); }); } /* AOS */ function initAOS() { if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; if (typeof AOS !== "undefined") { const isMobile = window.matchMedia("(max-width: 991.98px)").matches; if (isMobile) { document.querySelectorAll('[data-aos="fade-left"], [data-aos="fade-right"]').forEach((el) => { el.setAttribute("data-aos", "fade-up"); }); } AOS.init({ duration: 650, easing: "ease-out-cubic", once: true, offset: 60, disable: false, }); } } /* Year helpers already in footer */ function initPageNetworks() { const canvases = document.querySelectorAll(".page-net-canvas"); if (!canvases.length) return; const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; const BLUE = "#4ba3dc"; const ORANGE = "#f88820"; canvases.forEach((canvas) => { const section = canvas.closest(".page-hero"); const ctx = canvas.getContext("2d"); if (!ctx || !section) return; let nodes = []; let raf = 0; let width = 0; let height = 0; function resize() { const rect = section.getBoundingClientRect(); const dpr = Math.min(window.devicePixelRatio || 1, 2); width = Math.max(1, Math.floor(rect.width)); height = Math.max(1, Math.floor(rect.height)); canvas.width = Math.floor(width * dpr); canvas.height = Math.floor(height * dpr); canvas.style.width = `${width}px`; canvas.style.height = `${height}px`; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); seedNodes(); } function seedNodes() { const count = Math.max(28, Math.min(56, Math.floor((width * height) / 18000))); nodes = Array.from({ length: count }, (_, i) => { const accent = i % 9 === 0; return { x: Math.random() * width, y: Math.random() * height, vx: (Math.random() - 0.5) * (reduceMotion ? 0 : 0.35), vy: (Math.random() - 0.5) * (reduceMotion ? 0 : 0.35), r: accent ? 2.8 : 1.6 + Math.random() * 1.4, color: accent ? ORANGE : BLUE, }; }); } function draw() { ctx.clearRect(0, 0, width, height); const linkDist = Math.min(160, Math.max(110, width * 0.12)); for (let i = 0; i < nodes.length; i++) { const a = nodes[i]; if (!reduceMotion) { a.x += a.vx; a.y += a.vy; if (a.x < 0 || a.x > width) a.vx *= -1; if (a.y < 0 || a.y > height) a.vy *= -1; a.x = Math.max(0, Math.min(width, a.x)); a.y = Math.max(0, Math.min(height, a.y)); } for (let j = i + 1; j < nodes.length; j++) { const b = nodes[j]; const dx = a.x - b.x; const dy = a.y - b.y; const dist = Math.hypot(dx, dy); if (dist < linkDist) { const alpha = (1 - dist / linkDist) * 0.45; ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.strokeStyle = `rgba(160, 200, 230, ${alpha})`; ctx.lineWidth = 1; ctx.stroke(); } } ctx.beginPath(); ctx.arc(a.x, a.y, a.r, 0, Math.PI * 2); ctx.fillStyle = a.color; ctx.shadowColor = a.color; ctx.shadowBlur = a.color === ORANGE ? 10 : 6; ctx.fill(); ctx.shadowBlur = 0; } if (!reduceMotion) raf = requestAnimationFrame(draw); } resize(); draw(); window.addEventListener("resize", () => { cancelAnimationFrame(raf); resize(); draw(); }); }); } document.addEventListener("DOMContentLoaded", () => { renderHeader(); renderFooter(); renderChromeExtras(); initTheme(); initMobileNav(); initScrollUI(); initForms(); initAOS(); initPageNetworks(); initCareerApply(); initCookieBanner(); }); function initCookieBanner() { if (document.getElementById("cookieBanner")) return; let choice = ""; try { choice = localStorage.getItem("zs-analytics") || ""; } catch (_) { choice = ""; } if (choice === "granted" || choice === "denied") return; const banner = document.createElement("div"); banner.className = "cookie-banner"; banner.id = "cookieBanner"; banner.setAttribute("role", "dialog"); banner.setAttribute("aria-label", "Cookie consent"); banner.innerHTML = `

    We use optional analytics cookies (Google Analytics) to understand how the site is used. Your theme preference is stored on this device. Read the Privacy Policy.

    `; document.body.appendChild(banner); document.body.classList.add("has-cookie-banner"); const setChoice = (value) => { try { localStorage.setItem("zs-analytics", value); } catch (_) {} banner.remove(); document.body.classList.remove("has-cookie-banner"); if (value === "granted" && typeof window.zsLoadAnalytics === "function") { window.zsLoadAnalytics(); } }; document.getElementById("cookieAccept")?.addEventListener("click", () => setChoice("granted")); document.getElementById("cookieDecline")?.addEventListener("click", () => setChoice("denied")); } function initCareerApply() { const select = document.getElementById("c-position"); if (!select) return; document.querySelectorAll(".job-card a[href='#apply']").forEach((link) => { link.addEventListener("click", () => { const title = link.closest(".job-card")?.querySelector("h3")?.textContent.trim(); if (!title) return; const match = [...select.options].some((opt) => opt.value === title); if (match) { select.value = title; select.dispatchEvent(new Event("change", { bubbles: true })); select.focus({ preventScroll: true }); } }); }); } })();