Zettai_Systems_Website/js/main.js
2026-09-18 14:52:33 +05:30

678 lines
25 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 `<li><a class="${active.trim()}" href="${pageHref(item.href)}" ${PAGE === item.id ? 'aria-current="page"' : ""}>${item.label}</a></li>`;
}
function renderHeader() {
const mount = document.getElementById("site-header");
if (!mount) return;
mount.innerHTML = `
<a class="skip-link" href="#main">Skip to content</a>
<div class="site-header" id="headerBar">
<div class="container-zs navbar-zs">
<a class="brand-logo" href="${pageHref("")}" aria-label="Zettai Systems home">
<img class="logo-light" src="${asset("logos/logo-light-transparent.png")}" alt="Zettai Systems" height="44" />
<img class="logo-dark" src="${asset("logos/logo-dark-transparent.png")}" alt="Zettai Systems" height="44" />
</a>
<ul class="nav-links" role="list">
${NAV_ITEMS.map(navLink).join("")}
</ul>
<div class="nav-actions">
<button type="button" class="theme-toggle" id="themeToggle" aria-label="Toggle color theme" title="Toggle theme">
<i class="bi bi-sun-fill icon-sun" aria-hidden="true"></i>
<i class="bi bi-moon-stars-fill icon-moon" aria-hidden="true"></i>
</button>
<a class="btn-zs btn-zs-primary btn-zs-sm nav-cta nav-cta-full" href="${pageHref("contact")}">Consult with an Architect <span class="arrow" aria-hidden="true">→</span></a>
<a class="btn-zs btn-zs-primary btn-zs-sm nav-cta-compact" href="${pageHref("contact")}">Consult <span class="arrow" aria-hidden="true">→</span></a>
<button type="button" class="menu-toggle" id="menuToggle" aria-label="Open menu" aria-expanded="false" aria-controls="mobileDrawer">
<i class="bi bi-list" style="font-size:1.4rem" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<nav class="mobile-drawer" id="mobileDrawer" aria-label="Mobile" hidden>
<ul class="mobile-links">
${NAV_ITEMS.map((item) => {
const active = PAGE === item.id ? " active" : "";
return `<li><a class="${active.trim()}" href="${pageHref(item.href)}">${item.label}</a></li>`;
}).join("")}
</ul>
<a class="btn-zs btn-zs-primary w-100" href="${pageHref("contact")}">Consult with an Architect <span class="arrow">→</span></a>
</nav>
`;
}
function renderFooter() {
const mount = document.getElementById("site-footer");
if (!mount) return;
mount.innerHTML = `
<footer class="site-footer">
<div class="container-zs">
<div class="row g-4">
<div class="col-lg-4">
<div class="footer-brand">
<img src="${asset("logos/logo-dark-transparent.png")}" alt="Zettai Systems" height="44" />
<p class="footer-formerly">(Formerly Venba Information Technology)</p>
<p>Modern IT and digital solutions for businesses that need reliable software, scalable architecture, and long-term technology partnership.</p>
</div>
</div>
<div class="col-6 col-md-4 col-lg-2 footer-col">
<h4>Company</h4>
<ul>
<li><a href="${pageHref("about")}">About Us</a></li>
<li><a href="${pageHref("careers")}">Careers</a></li>
<li><a href="${pageHref("contact")}">Contact Us</a></li>
</ul>
</div>
<div class="col-6 col-md-4 col-lg-3 footer-col">
<h4>Services</h4>
<ul>
<li><a href="${pageHref("services/agentic-ai")}">Agentic Workflows</a></li>
<li><a href="${pageHref("services/enterprise-rag")}">Domain RAG</a></li>
<li><a href="${pageHref("services/ai-microservices")}">AI Microservices</a></li>
<li><a href="${pageHref("services/intelligent-process-automation")}">Process Automation</a></li>
<li><a href="${pageHref("services/ai-governance")}">AI Governance</a></li>
<li><a href="${pageHref("services/web-development")}">Web Platforms</a></li>
<li><a href="${pageHref("services/mobile-development")}">Flutter Mobile</a></li>
<li><a href="${pageHref("services/cloud-integration")}">Cloud &amp; MLOps</a></li>
<li><a href="${pageHref("services/custom-software")}">Custom Software</a></li>
<li><a href="${pageHref("services/data-analytics")}">Data Analytics</a></li>
<li><a href="${pageHref("services/it-consulting")}">IT Consulting</a></li>
<li><a href="${pageHref("services/cybersecurity")}">Cybersecurity</a></li>
<li><a href="${pageHref("services/erp")}">ERP</a></li>
</ul>
</div>
<div class="col-md-4 col-lg-3 footer-col">
<h4>Solutions</h4>
<ul>
<li><a href="${pageHref("solutions")}#digital-transformation">Digital Transformation</a></li>
<li><a href="${pageHref("solutions")}#business-automation">Business Automation</a></li>
<li><a href="${pageHref("solutions")}#enterprise">Enterprise Solutions</a></li>
<li><a href="${pageHref("services/ecommerce")}">E-Commerce</a></li>
</ul>
<h4 class="mt-4">Contact</h4>
<ul class="footer-contact">
<li>
<i class="bi bi-telephone-fill" aria-hidden="true"></i>
<a href="tel:+918072998024" aria-label="Call Zettai Systems at +91 807 299 8024">+91 807 299 8024</a>
</li>
<li>
<i class="bi bi-geo-alt-fill" aria-hidden="true"></i>
<span>No.10/21, 27th Street, Thillai Ganga Nagar, Nanganallur, Chennai 600061, Tamil Nadu, India</span>
</li>
<li>
<i class="bi bi-envelope-fill" aria-hidden="true"></i>
<a href="mailto:solutions@zettaisystems.com">solutions@zettaisystems.com</a>
</li>
</ul>
</div>
</div>
<div class="footer-bottom">
<div>© ${new Date().getFullYear()} Zettai Systems Pvt Ltd. All Rights Reserved.</div>
<div class="footer-legal">
<a href="${pageHref("privacy-policy")}">Privacy Policy</a>
<a href="${pageHref("terms")}">Terms &amp; Conditions</a>
</div>
</div>
</div>
</footer>
`;
}
function renderChromeExtras() {
if (document.getElementById("floatingCta")) return;
const frag = document.createElement("div");
frag.innerHTML = `
<a class="floating-cta" id="floatingCta" href="${pageHref("contact")}">
<i class="bi bi-chat-dots-fill" aria-hidden="true"></i>
<span class="label-full">Let's Talk</span>
<span class="d-sm-none">Talk</span>
<span class="arrow" aria-hidden="true">→</span>
</a>
<button type="button" class="back-to-top" id="backToTop" aria-label="Back to top">
<i class="bi bi-arrow-up" aria-hidden="true"></i>
</button>
`;
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 = `
<p>We use optional analytics cookies (Google Analytics) to understand how the site is used. Your theme preference is stored on this device. Read the <a href="${pageHref("privacy-policy")}">Privacy Policy</a>.</p>
<div class="cookie-banner-actions">
<button type="button" class="btn-zs btn-zs-outline btn-zs-sm" id="cookieDecline">Decline</button>
<button type="button" class="btn-zs btn-zs-primary btn-zs-sm" id="cookieAccept">Accept</button>
</div>
`;
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 });
}
});
});
}
})();