diff --git a/.htaccess b/.htaccess index a1dafd3..04ccfec 100644 --- a/.htaccess +++ b/.htaccess @@ -36,6 +36,11 @@ Options -Indexes # /services/ → /services (fixes broken relative CSS under trailing slash) RewriteRule ^services/$ /services [R=301,L] + # Nested extensionless pages: /services/foo/ → /services/foo + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{DOCUMENT_ROOT}/$1.html -f + RewriteRule ^(.+)/$ /$1 [R=301,L] + # /services → services.html (page, not the services/ directory) RewriteRule ^services$ services.html [L] @@ -48,6 +53,10 @@ Options -Indexes # Safe security headers (do not block static assets) - Header set X-Content-Type-Options "nosniff" - Header set Referrer-Policy "strict-origin-when-cross-origin" + Header always set X-Content-Type-Options "nosniff" + Header always set Referrer-Policy "strict-origin-when-cross-origin" + Header always set X-Frame-Options "SAMEORIGIN" + Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()" + Header always set Content-Security-Policy "frame-ancestors 'self'; base-uri 'self'; object-src 'none'; form-action 'self'" + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" diff --git a/404.html b/404.html index 65d8949..0b1d0f1 100644 --- a/404.html +++ b/404.html @@ -12,7 +12,7 @@ - + diff --git a/about.html b/about.html index a51d7fe..ea66bf5 100644 --- a/about.html +++ b/about.html @@ -11,7 +11,7 @@ - + diff --git a/api/careers.php b/api/careers.php index 98fe2e7..a1814e4 100644 --- a/api/careers.php +++ b/api/careers.php @@ -13,6 +13,9 @@ if (zs_is_honeypot_filled()) { zs_json_response(200, ['ok' => true, 'message' => 'Thank you. Your application has been received.']); } +zs_require_csrf(); +zs_rate_limit('careers'); + $name = zs_field('name', 120); $email = zs_field('email', 160); $phone = zs_field('phone', 40); diff --git a/api/contact.php b/api/contact.php index 892fc7d..53fe137 100644 --- a/api/contact.php +++ b/api/contact.php @@ -13,6 +13,9 @@ if (zs_is_honeypot_filled()) { zs_json_response(200, ['ok' => true, 'message' => 'Thank you. We have received your message.']); } +zs_require_csrf(); +zs_rate_limit('contact'); + $name = zs_field('name', 120); $email = zs_field('email', 160); $phone = zs_field('phone', 40); diff --git a/api/helpers.php b/api/helpers.php index 0bc5825..2b1bd96 100644 --- a/api/helpers.php +++ b/api/helpers.php @@ -10,6 +10,8 @@ const ZS_CAREERS_TO = 'careers@zettaisystems.com'; const ZS_FROM_NAME = 'Zettai Systems Website'; const ZS_FROM_EMAIL = 'noreply@zettaisystems.com'; const ZS_MAX_RESUME_BYTES = 5 * 1024 * 1024; // 5 MB +const ZS_RL_MAX = 5; +const ZS_RL_WINDOW = 900; // 15 minutes function zs_json_response(int $status, array $payload): void { @@ -27,6 +29,100 @@ function zs_require_post(): void } } +function zs_request_host(): string +{ + $host = $_SERVER['HTTP_HOST'] ?? ''; + if (!is_string($host) || $host === '') { + return ''; + } + return strtolower((string) preg_replace('/:\d+$/', '', $host)); +} + +function zs_require_csrf(): void +{ + $host = zs_request_host(); + $origin = $_SERVER['HTTP_ORIGIN'] ?? ''; + if (is_string($origin) && $origin !== '') { + $originHost = parse_url($origin, PHP_URL_HOST); + if (!is_string($originHost) || strtolower($originHost) !== $host) { + zs_json_response(403, [ + 'ok' => false, + 'error' => 'Security check failed. Please refresh the page and try again.', + ]); + } + } + + $cookie = $_COOKIE['zs_csrf'] ?? ''; + $posted = $_POST['csrf_token'] ?? ''; + if ( + !is_string($cookie) || + !is_string($posted) || + $cookie === '' || + $posted === '' || + !hash_equals($cookie, $posted) + ) { + zs_json_response(403, [ + 'ok' => false, + 'error' => 'Security check failed. Please refresh the page and try again.', + ]); + } +} + +function zs_rate_limit(string $bucket): void +{ + $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; + if (!is_string($ip) || $ip === '') { + $ip = '0.0.0.0'; + } + + $dir = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'zs_form_rl'; + if (!is_dir($dir) && !@mkdir($dir, 0700, true) && !is_dir($dir)) { + return; + } + + $file = $dir . DIRECTORY_SEPARATOR . hash('sha256', $bucket . '|' . $ip); + $fh = @fopen($file, 'c+'); + if ($fh === false) { + return; + } + + flock($fh, LOCK_EX); + $raw = stream_get_contents($fh); + $hits = []; + if (is_string($raw) && $raw !== '') { + $decoded = json_decode($raw, true); + if (is_array($decoded)) { + $hits = $decoded; + } + } + + $now = time(); + $windowStart = $now - ZS_RL_WINDOW; + $fresh = []; + foreach ($hits as $stamp) { + if (is_int($stamp) && $stamp > $windowStart) { + $fresh[] = $stamp; + } + } + + if (count($fresh) >= ZS_RL_MAX) { + flock($fh, LOCK_UN); + fclose($fh); + zs_json_response(429, [ + 'ok' => false, + 'error' => 'Too many submissions. Please wait a few minutes and try again.', + ]); + } + + $fresh[] = $now; + rewind($fh); + ftruncate($fh, 0); + fwrite($fh, json_encode($fresh)); + fflush($fh); + flock($fh, LOCK_UN); + fclose($fh); +} + function zs_field(string $key, int $max = 2000): string { $value = $_POST[$key] ?? ''; diff --git a/careers.html b/careers.html index 6c239e7..ec4467a 100644 --- a/careers.html +++ b/careers.html @@ -11,7 +11,7 @@ - + diff --git a/contact.html b/contact.html index b705358..649d68d 100644 --- a/contact.html +++ b/contact.html @@ -11,7 +11,7 @@ - + diff --git a/css/style.css b/css/style.css index 138a71b..733b9db 100644 --- a/css/style.css +++ b/css/style.css @@ -2404,6 +2404,46 @@ body[data-page="contact"] .back-to-top { top: 1rem; } +.cookie-banner { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 2100; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 0.85rem 1.25rem; + padding: 0.95rem 1.25rem; + background: var(--surface-elevated); + color: var(--text-color); + border-top: 1px solid var(--border-color); +} + +.cookie-banner p { + margin: 0; + flex: 1 1 16rem; + font-size: 0.92rem; + color: var(--muted-text-color); +} + +.cookie-banner a { + color: var(--primary-color); + text-decoration: underline; +} + +.cookie-banner-actions { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +body.has-cookie-banner .floating-cta, +body.has-cookie-banner .back-to-top { + display: none !important; +} + /* Service detail extras */ .benefit-list { list-style: none; diff --git a/index.html b/index.html index f2bc1f7..69cb6cb 100644 --- a/index.html +++ b/index.html @@ -11,7 +11,7 @@ - + @@ -113,7 +113,7 @@
Secure Architecture
Cloud-Ready Delivery
- + - + diff --git a/js/analytics.js b/js/analytics.js index 9c00613..148e20a 100644 --- a/js/analytics.js +++ b/js/analytics.js @@ -1,23 +1,40 @@ -/* Google Analytics 4 — G-1JR9P2WP0B */ +/* Google Analytics 4 — G-1JR9P2WP0B (loads only after cookie consent) */ (function () { "use strict"; var MEASUREMENT_ID = "G-1JR9P2WP0B"; - var host = location.hostname; - var isLocal = host === "localhost" || host === "127.0.0.1"; window.dataLayer = window.dataLayer || []; window.gtag = function gtag() { window.dataLayer.push(arguments); }; - if (isLocal) return; + function isLocalHost() { + var host = location.hostname; + return host === "localhost" || host === "127.0.0.1"; + } - window.gtag("js", new Date()); - window.gtag("config", MEASUREMENT_ID); + function loadGa() { + if (window.__zsGaLoaded || isLocalHost()) return; - var script = document.createElement("script"); - script.async = true; - script.src = "https://www.googletagmanager.com/gtag/js?id=" + encodeURIComponent(MEASUREMENT_ID); - document.head.appendChild(script); + var consent = ""; + try { + consent = localStorage.getItem("zs-analytics") || ""; + } catch (e) { + consent = ""; + } + if (consent !== "granted") return; + + window.__zsGaLoaded = true; + window.gtag("js", new Date()); + window.gtag("config", MEASUREMENT_ID); + + var script = document.createElement("script"); + script.async = true; + script.src = "https://www.googletagmanager.com/gtag/js?id=" + encodeURIComponent(MEASUREMENT_ID); + document.head.appendChild(script); + } + + window.zsLoadAnalytics = loadGa; + loadGa(); })(); diff --git a/js/main.js b/js/main.js index 853e911..b29dd2d 100644 --- a/js/main.js +++ b/js/main.js @@ -208,7 +208,10 @@ 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"); @@ -217,6 +220,7 @@ const icon = toggle.querySelector("i"); if (icon) icon.className = "bi bi-list"; icon && (icon.style.fontSize = "1.4rem"); + if (wasOpen) toggle.focus(); }; const open = () => { @@ -228,6 +232,13 @@ 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", () => { @@ -239,6 +250,27 @@ 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 */ @@ -329,6 +361,12 @@ 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."; } @@ -341,7 +379,18 @@ 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(); @@ -384,9 +433,11 @@ } try { + const formData = new FormData(form); + formData.set("csrf_token", csrfToken()); const { response, data, raw } = await postForm( formEndpoint(kind), - new FormData(form), + formData, false ); @@ -563,8 +614,49 @@ 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; diff --git a/privacy-policy.html b/privacy-policy.html index 311ca6c..ff0c6c7 100644 --- a/privacy-policy.html +++ b/privacy-policy.html @@ -11,7 +11,7 @@ - + @@ -84,7 +84,7 @@

We do not sell personal information.

4. Cookies and similar technologies

-

Our site uses cookies or local storage for functional purposes (for example, remembering your theme preference). We also use Google Analytics to understand how visitors use the site (pages viewed, approximate location, device, and browser). Google may set cookies as described in Google’s privacy policy. You can control cookies through your browser settings.

+

Our site uses cookies or local storage for functional purposes (for example, remembering your theme preference). We also use Google Analytics to understand how visitors use the site (pages viewed, approximate location, device, and browser), only after you accept analytics cookies in the site banner. Google may set cookies as described in Google’s privacy policy. You can change your choice by clearing site data, or control cookies through your browser settings.

5. Sharing of information

We may share information with trusted service providers who help us operate our website, email, hosting, or form processing—only as needed to perform those services. We may also disclose information if required by law or to protect our rights, users, or systems.

diff --git a/serve.py b/serve.py index 3a1ca71..17d5c5c 100644 --- a/serve.py +++ b/serve.py @@ -11,6 +11,7 @@ Then open http://127.0.0.1:8080/about (not file://) from __future__ import annotations import argparse +import json import mimetypes import os from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer @@ -20,6 +21,17 @@ from urllib.parse import unquote, urlparse ROOT = Path(__file__).resolve().parent +SECURITY_HEADERS = ( + ("X-Content-Type-Options", "nosniff"), + ("Referrer-Policy", "strict-origin-when-cross-origin"), + ("X-Frame-Options", "SAMEORIGIN"), + ("Permissions-Policy", "camera=(), microphone=(), geolocation=()"), + ( + "Content-Security-Policy", + "frame-ancestors 'self'; base-uri 'self'; object-src 'none'; form-action 'self'", + ), +) + class CleanURLHandler(SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): @@ -39,9 +51,32 @@ class CleanURLHandler(SimpleHTTPRequestHandler): self.send_header("Location", location) self.end_headers() + def _json(self, status: int, payload: dict) -> None: + body = json.dumps(payload).encode("utf-8") + self.send_response(status) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + if self.command != "HEAD": + self.wfile.write(body) + + def _serve_404(self) -> None: + page = ROOT / "404.html" + data = page.read_bytes() if page.is_file() else b"Not found" + self.send_response(404) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + if self.command != "HEAD": + self.wfile.write(data) + def _maybe_redirect(self) -> bool: path, query = self._parts() + if path in ("/services/odoo", "/services/odoo/", "/services/odoo.html"): + self._redirect("/services/erp" + query) + return True + if path in ("/index.html", "/index"): self._redirect("/" + query) return True @@ -51,26 +86,62 @@ class CleanURLHandler(SimpleHTTPRequestHandler): self._redirect(clean + query) return True - # /services/ → /services (same as Apache — trailing slash breaks relative CSS) - if path == "/services/": - self._redirect("/services" + query) + # Trailing slash on pages (and /services/) → slashless URL + if path != "/" and path.endswith("/"): + self._redirect(path.rstrip("/") + query) return True return False + def _handle_php(self) -> bool: + path, _query = self._parts() + clean = path.rstrip("/") or "/" + if not clean.endswith(".php"): + return False + if clean.endswith("/api/helpers.php") or clean == "/helpers.php": + self.send_error(403, "Forbidden") + return True + if self.command == "POST": + self.send_error(501, "PHP is not available on the local static server") + return True + self._json(405, {"ok": False, "error": "Method not allowed."}) + return True + def do_GET(self): if self._maybe_redirect(): return + if self._handle_php(): + return + translated = Path(self.translate_path(self.path)) + if not self._is_servable(translated): + self._serve_404() + return return super().do_GET() def do_HEAD(self): if self._maybe_redirect(): return + if self._handle_php(): + return + translated = Path(self.translate_path(self.path)) + if not self._is_servable(translated): + self._serve_404() + return return super().do_HEAD() + def do_POST(self): + if self._handle_php(): + return + self.send_error(501, "Unsupported method") + + def _is_servable(self, translated: Path) -> bool: + if translated.is_file(): + return True + # Directory with index.html (site root /) + return translated.is_dir() and (translated / "index.html").is_file() + def list_directory(self, path): - # Never show raw directory indexes - self.send_error(404, "Not found") + self._serve_404() return None def translate_path(self, path: str) -> str: @@ -93,6 +164,8 @@ class CleanURLHandler(SimpleHTTPRequestHandler): def end_headers(self): self.send_header("Cache-Control", "no-store") + for name, value in SECURITY_HEADERS: + self.send_header(name, value) super().end_headers() @@ -119,9 +192,9 @@ def main() -> None: raise SystemExit(1) from exc raise - print(f"Serving {ROOT}") - print(f"Open http://127.0.0.1:{args.port}/services") - print("Press Ctrl+C to stop") + print(f"Serving {ROOT}", flush=True) + print(f"Open http://127.0.0.1:{args.port}/services", flush=True) + print("Press Ctrl+C to stop", flush=True) try: server.serve_forever() except KeyboardInterrupt: diff --git a/services.html b/services.html index ea043a3..0abe2e6 100644 --- a/services.html +++ b/services.html @@ -11,7 +11,7 @@ - + diff --git a/services/agentic-ai.html b/services/agentic-ai.html index a3522e1..810e227 100644 --- a/services/agentic-ai.html +++ b/services/agentic-ai.html @@ -2,7 +2,7 @@ - + Generative AI & Agentic Workflows | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -211,9 +211,9 @@ - - - - + + + + diff --git a/services/ai-governance.html b/services/ai-governance.html index f3f5d68..557f4cb 100644 --- a/services/ai-governance.html +++ b/services/ai-governance.html @@ -2,7 +2,7 @@ - + AI Governance | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -211,9 +211,9 @@ - - - - + + + + diff --git a/services/ai-microservices.html b/services/ai-microservices.html index 00a732c..6d6ac24 100644 --- a/services/ai-microservices.html +++ b/services/ai-microservices.html @@ -2,7 +2,7 @@ - + Custom AI Microservices & APIs | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -188,9 +188,9 @@ - - - - + + + + diff --git a/services/cloud-integration.html b/services/cloud-integration.html index bd75eec..a2d5526 100644 --- a/services/cloud-integration.html +++ b/services/cloud-integration.html @@ -2,7 +2,7 @@ - + Cloud, MLOps & QA Engineering | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -221,9 +221,9 @@ - - - - + + + + diff --git a/services/custom-software.html b/services/custom-software.html index e92e4de..2ecd93c 100644 --- a/services/custom-software.html +++ b/services/custom-software.html @@ -2,7 +2,7 @@ - + Custom Software Development | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -221,9 +221,9 @@ - - - - + + + + diff --git a/services/cybersecurity.html b/services/cybersecurity.html index c12b43e..2c815c2 100644 --- a/services/cybersecurity.html +++ b/services/cybersecurity.html @@ -2,7 +2,7 @@ - + Cybersecurity | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -221,9 +221,9 @@ - - - - + + + + diff --git a/services/data-analytics.html b/services/data-analytics.html index 87e1447..af28d8d 100644 --- a/services/data-analytics.html +++ b/services/data-analytics.html @@ -2,7 +2,7 @@ - + Data Analytics | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -221,9 +221,9 @@ - - - - + + + + diff --git a/services/ecommerce.html b/services/ecommerce.html index b9f7a69..5fb0189 100644 --- a/services/ecommerce.html +++ b/services/ecommerce.html @@ -2,7 +2,7 @@ - + E-Commerce Solutions | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -221,9 +221,9 @@ - - - - + + + + diff --git a/services/enterprise-rag.html b/services/enterprise-rag.html index c484997..c781af0 100644 --- a/services/enterprise-rag.html +++ b/services/enterprise-rag.html @@ -2,7 +2,7 @@ - + Domain RAG & Enterprise Search | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -211,9 +211,9 @@ - - - - + + + + diff --git a/services/erp.html b/services/erp.html index abbb556..ed13c06 100644 --- a/services/erp.html +++ b/services/erp.html @@ -2,7 +2,7 @@ - + ERP Solutions | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -221,9 +221,9 @@ - - - - + + + + diff --git a/services/intelligent-process-automation.html b/services/intelligent-process-automation.html index 8eed848..c1c9101 100644 --- a/services/intelligent-process-automation.html +++ b/services/intelligent-process-automation.html @@ -2,7 +2,7 @@ - + Intelligent Process Automation | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -188,9 +188,9 @@ - - - - + + + + diff --git a/services/it-consulting.html b/services/it-consulting.html index feb3281..dd81294 100644 --- a/services/it-consulting.html +++ b/services/it-consulting.html @@ -2,7 +2,7 @@ - + IT Consulting | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -221,9 +221,9 @@ - - - - + + + + diff --git a/services/mobile-development.html b/services/mobile-development.html index 5def591..b9120ab 100644 --- a/services/mobile-development.html +++ b/services/mobile-development.html @@ -2,7 +2,7 @@ - + High-Performance Mobile (Flutter) | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -221,9 +221,9 @@ - - - - + + + + diff --git a/services/web-development.html b/services/web-development.html index 2fd4fe1..c499027 100644 --- a/services/web-development.html +++ b/services/web-development.html @@ -2,7 +2,7 @@ - + Modern Full-Stack Web Platforms | Zettai Systems @@ -11,14 +11,14 @@ - + - - - + + + - - + + @@ -221,9 +221,9 @@ - - - - + + + + diff --git a/sitemap.xml b/sitemap.xml index cab2931..4cdddf2 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -13,6 +13,7 @@ https://zettaisystems.com/services/agentic-ai https://zettaisystems.com/services/enterprise-rag https://zettaisystems.com/services/ai-microservices + https://zettaisystems.com/services/ai-governance https://zettaisystems.com/services/intelligent-process-automation https://zettaisystems.com/services/mobile-development https://zettaisystems.com/services/custom-software diff --git a/solutions.html b/solutions.html index 1567f1d..e4a14f0 100644 --- a/solutions.html +++ b/solutions.html @@ -11,7 +11,7 @@ - + diff --git a/terms.html b/terms.html index ec4c50a..071463d 100644 --- a/terms.html +++ b/terms.html @@ -11,7 +11,7 @@ - +