جاري التحميل…
ابدأ مشروعك

أتمتة أعمالك

اضبطها مرة، وتعمل دائمًا لصالحك.

تبني Doy.Tech بنية سحابية ومنتجات رقمية تُزيل الأعمال المتكررة وتمنح الفرق الطموحة سرعة نمو مستقرة.

ماذا نبني تقنيًا

تنفيذ هندسي متكامل من البداية للنهاية، يركز على الأتمتة والموثوقية والأثر التجاري القابل للقياس.

01

أنظمة الأتمتة

استبدال العمل اليدوي بأنظمة مترابطة تقلل الأخطاء وتعيد للفريق وقتًا فعليًا.

  • تحليل وبناء مسارات العمل
  • تكامل بين الأنظمة والأدوات
02

منصات ويب مخصصة

منتجات ويب مصممة للسرعة وسهولة التطوير والاستمرار بثقة.

  • تطوير أنظمة إدارة المحتوى والتطبيقات
  • معمارية تعتمد على واجهات API
03

موثوقية سحابية

بنية تحتية ورقابة تشغيلية تحافظ على خدماتك مستقرة تحت الضغط.

  • تقوية البنية والحوكمة الأمنية
  • استراتيجية نسخ احتياطي وتعافي

أثرنا بالأرقام

مؤشرات مرجعية تعكس حجم التنفيذ والموثوقية التي تقدمها Doy.Tech لشركائها.

0 الموظفون
0 عملاء سعداء
0 سنوات خبرة
0 مكاتب حول العالم

عملاء وشركاء

نماذج من الجهات التي تعتمد على التنفيذ الرقمي عالي الجودة.

مجموعة تجارة عالمية
تحالف التقنية المالية
منصة أنظمة قانونية
CloudScale الشرق الأوسط
نوفا للتوزيع
مختبر الإعلام المؤسسي

مشاريعنا

يتم تحميل المشاريع تلقائيًا من ملفات Markdown داخل مجلد الصفحات لضمان تحديث سهل وسريع.

جاري تحميل المشاريع...

نقوم بقراءة ملفات المشروع من صفحات الموقع.

أحدث المقالات

معرفة عملية من منظومة Doy.Tech تركز على التنفيذ الرقمي الحقيقي.

Bonjourr طوال اليوم

نظرة على أدوات صفحات البداية وكيف ترفع الإنتاجية اليومية بخطوات بسيطة.

// ----------------------------------------------------- // Standard Interactive Mechanics // ----------------------------------------------------- (function () { // Language preference: URL -> saved user choice -> browser -> default Arabic const supportedLangs = ["ar", "en"]; const i18n = { ar: { pageTitle: "Doy.Tech | أتمتة أعمالك بذكاء", pageDescription: "تساعد Doy.Tech الفرق على أتمتة العمليات عبر الهندسة والموثوقية السحابية والأنظمة الرقمية المخصصة التي تُبنى مرة واحدة وتتوسع بثبات.", navServices: "الخدمات", navNumbers: "الأرقام", navClients: "العملاء", navProjects: "المشاريع", navBlog: "المدونة", navContact: "تواصل", ctaTop: "ابدأ مشروعك", menuOpenLabel: "فتح القائمة", heroEyebrow: "عملاء ذكاء اصطناعي رهن الإشارة", heroTitle: "اضبطها مرة، وتعمل دائمًا لصالحك.", heroLead: "تبني Doy.Tech بنية سحابية ومنتجات رقمية تُزيل الأعمال المتكررة وتمنح الفرق الطموحة سرعة نمو مستقرة.", heroPrimary: "استكشف الخدمات", heroSecondary: "تحدث مع Doy.Tech", servicesTitle: "ماذا نبني تقنيًا", servicesLead: "تنفيذ هندسي متكامل من البداية للنهاية، يركز على الأتمتة والموثوقية والأثر التجاري القابل للقياس.", service1Title: "أنظمة الأتمتة", service1Desc: "استبدال العمل اليدوي بأنظمة مترابطة تقلل الأخطاء وتعيد للفريق وقتًا فعليًا.", service1Item1: "تحليل وبناء مسارات العمل", service1Item2: "تكامل بين الأنظمة والأدوات", service2Title: "منصات ويب مخصصة", service2Desc: "منتجات ويب مصممة للسرعة وسهولة التطوير والاستمرار بثقة.", service2Item1: "تطوير أنظمة إدارة المحتوى والتطبيقات", service2Item2: "معمارية تعتمد على واجهات API", service3Title: "موثوقية سحابية", service3Desc: "بنية تحتية ورقابة تشغيلية تحافظ على خدماتك مستقرة تحت الضغط.", service3Item1: "تقوية البنية والحوكمة الأمنية", service3Item2: "استراتيجية نسخ احتياطي وتعافي", numbersTitle: "أثرنا بالأرقام", numbersLead: "مؤشرات مرجعية تعكس حجم التنفيذ والموثوقية التي تقدمها Doy.Tech لشركائها.", statEmployees: "الموظفون", statClients: "عملاء سعداء", statYears: "سنوات خبرة", statOffices: "مكاتب حول العالم", clientsTitle: "عملاء وشركاء", clientsLead: "نماذج من الجهات التي تعتمد على التنفيذ الرقمي عالي الجودة.", client1: "مجموعة تجارة عالمية", client2: "تحالف التقنية المالية", client3: "منصة أنظمة قانونية", client4: "CloudScale الشرق الأوسط", client5: "نوفا للتوزيع", client6: "مختبر الإعلام المؤسسي", projectsTitle: "مشاريعنا", projectsLead: "يتم تحميل المشاريع تلقائيًا من ملفات Markdown داخل مجلد الصفحات لضمان تحديث سهل وسريع.", projectsLoadingTitle: "جاري تحميل المشاريع...", projectsLoadingDesc: "نقوم بقراءة ملفات المشروع من صفحات الموقع.", blogTitle: "أحدث المقالات", blogLead: "معرفة عملية من منظومة Doy.Tech تركز على التنفيذ الرقمي الحقيقي.", article1Title: "ما هو البث المباشر باستخدام vMix؟", article1Desc: "شرح تطبيقي لقدرات vMix وكيف تبني الفرق بثًا احترافيًا بسرعة وثبات.", article2Title: "Bonjourr طوال اليوم", article2Desc: "نظرة على أدوات صفحات البداية وكيف ترفع الإنتاجية اليومية بخطوات بسيطة.", contactTitle: "ابدأ اليوم", contactLead: "اكتشف بنفسك كيف تغيّر الأتمتة طريقة إدارة أعمالك وحياتك اليومية.", contactLocationLabel: "الموقع", contactLocationValue: "المحرق، البحرين", contactPhoneLabel: "الهاتف", contactEmailLabel: "البريد الإلكتروني", contactPrimary: "ابدأ مشروعك الآن", contactWalkthrough: "جولة البدء بالمشروع", footerCopy: "© 2026 Doy.Tech. جميع الحقوق محفوظة. تنفيذ رقمي يصنع أثرًا فعليًا.", footerHome: "الرئيسية", footerMarket: "السوق", footerBlog: "المدونة", footerContact: "تواصل", walkTitle: "جولة سريعة لبدء مشروعك", walkClose: "إغلاق", walkStep1Title: "1) حدد هدفك التجاري", walkStep1Desc: "صف المشكلة الأساسية والنتيجة التي تريد الوصول إليها خلال 60-90 يومًا.", walkStep2Title: "2) شارك الأنظمة الحالية", walkStep2Desc: "اذكر أدواتك الحالية ومصادر البيانات لتحديد أسرع نقطة أتمتة ممكنة.", walkStep3Title: "3) احجز جلسة الانطلاق", walkStep3Desc: "نرتب خطة تنفيذ مرحلية واضحة ونبدأ بأول إنجاز قابل للقياس بسرعة.", walkDismiss: "لاحقًا", walkContinue: "الانتقال لصفحة بدء المشروع", projectsEmptyTitle: "لا توجد بيانات مشاريع حاليًا", projectsEmptyDesc: "تعذر تحميل ملف pages/PROJECTS.md.", projectsFallbackTitle: "مشاريع Doy.Tech", projectsReadMore: "قراءة تفاصيل المشروع", projectDefaultTitle: "مشروع", projectDefaultSummary: "تفاصيل المشروع متاحة ضمن صفحة المشروع الكاملة." }, en: { pageTitle: "Doy.Tech | Automate Your Business", pageDescription: "Doy.Tech helps teams automate operations with engineering, cloud reliability, and custom digital systems that are set up once and built to scale.", navServices: "Services", navNumbers: "Numbers", navClients: "Clients", navProjects: "Projects", navBlog: "Blog", navContact: "Contact", ctaTop: "Start a Project", menuOpenLabel: "Open menu", heroEyebrow: "Automate your business", heroTitle: "Setup once and works forever for you.", heroLead: "Doy.Tech builds cloud infrastructure and digital products that remove repetitive work and create dependable momentum for growth-minded teams.", heroPrimary: "Explore Services", heroSecondary: "Talk to Doy.Tech", servicesTitle: "What We Engineer", servicesLead: "End-to-end technical delivery focused on automation, reliability, and measurable business impact.", service1Title: "Automation Systems", service1Desc: "Replace manual workflows with integrated systems that reduce errors and recover team time.", service1Item1: "Business workflow mapping", service1Item2: "Integration between tools", service2Title: "Custom Web Platforms", service2Desc: "Web products engineered for speed, maintainability, and confident iteration.", service2Item1: "CMS and app development", service2Item2: "API-first architecture", service3Title: "Cloud Reliability", service3Desc: "Infrastructure and observability layers built to keep your services resilient.", service3Item1: "Infrastructure hardening", service3Item2: "Backup strategy", numbersTitle: "Our Work in Numbers", numbersLead: "Core business metrics reflected from Doy.Tech references and presented as social proof anchors.", statEmployees: "Employees", statClients: "Happy Clients", statYears: "Years of Experience", statOffices: "Offices Worldwide", clientsTitle: "Clients & Sponsors", clientsLead: "A sample presentation band for trusted brands and partnerships.", client1: "Global Retail Group", client2: "Fintech Alliance", client3: "Legal Systems Hub", client4: "CloudScale MENA", client5: "Nova Distribution", client6: "Enterprise Media Lab", projectsTitle: "Our Projects", projectsLead: "Projects are loaded automatically from Markdown files under the pages directory for easy updates.", projectsLoadingTitle: "Loading projects...", projectsLoadingDesc: "Reading project Markdown files from site pages.", blogTitle: "Latest Blogs", blogLead: "Knowledge from the Doy.Tech ecosystem, focused on practical digital execution.", article1Title: "What is Live Streaming using vMix?", article1Desc: "A practical walk-through of vMix capabilities and how teams can ship polished broadcasts.", article2Title: "Bonjourr: All Day Long!", article2Desc: "A look at useful startup-page tooling and lightweight productivity gains on the web.", contactTitle: "Start Today", contactLead: "Check for yourself how automation can drastically change your business and your life.", contactLocationLabel: "Location", contactLocationValue: "Muharraq, Bahrain", contactPhoneLabel: "Phone", contactEmailLabel: "Email", contactPrimary: "Get in Touch", contactWalkthrough: "Project Start Walkthrough", footerCopy: "© 2026 Doy.Tech. All rights reserved. Crafted for high-impact digital execution.", footerHome: "Home", footerMarket: "Market", footerBlog: "Blog", footerContact: "Contact", walkTitle: "Quick Walkthrough to Start Your Project", walkClose: "Close", walkStep1Title: "1) Define your business goal", walkStep1Desc: "Describe the core problem and the outcome you want to reach in 60-90 days.", walkStep2Title: "2) Share current systems", walkStep2Desc: "List your existing tools and data sources to identify the fastest automation opportunity.", walkStep3Title: "3) Book kickoff session", walkStep3Desc: "We prepare a phased plan and start with the first measurable delivery quickly.", walkDismiss: "Maybe later", walkContinue: "Go to Start a Project page", projectsEmptyTitle: "No project data available yet", projectsEmptyDesc: "Could not load pages/PROJECTS.md.", projectsFallbackTitle: "Doy.Tech Projects", projectsReadMore: "Read project details", projectDefaultTitle: "Project", projectDefaultSummary: "Project details are available in the full project page." } }; function detectLanguage() { const urlLang = new URLSearchParams(window.location.search).get("lang"); if (supportedLangs.includes(urlLang)) { localStorage.setItem("preferredLang", urlLang); return urlLang; } const savedLang = localStorage.getItem("preferredLang"); if (supportedLangs.includes(savedLang)) { return savedLang; } const browserLangs = (navigator.languages || [navigator.language || ""]).map(function (lang) { return String(lang).toLowerCase(); }); if (browserLangs.some(function (lang) { return lang.indexOf("ar") === 0; })) { return "ar"; } if (browserLangs.some(function (lang) { return lang.indexOf("en") === 0; })) { return "en"; } // Per requirement, Arabic is the fallback default. return "ar"; } let currentLang = detectLanguage(); let globeRotationOffset = 0; // 0 or Math.PI*2 for forward/backward function t(key) { const dict = i18n[currentLang] || i18n.ar; return dict[key] || i18n.ar[key] || key; } function setText(selector, value) { const el = document.querySelector(selector); if (el) el.textContent = value; } function setHtml(selector, value) { const el = document.querySelector(selector); if (el) el.innerHTML = value; } function updateLanguageButtons() { const arBtn = document.getElementById("langAr"); const enBtn = document.getElementById("langEn"); if (arBtn) arBtn.classList.toggle("is-active", currentLang === "ar"); if (enBtn) enBtn.classList.toggle("is-active", currentLang === "en"); } function applyDocumentLanguage(lang) { currentLang = supportedLangs.includes(lang) ? lang : "ar"; localStorage.setItem("preferredLang", currentLang); document.documentElement.lang = currentLang; document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr"; updateLanguageButtons(); } function applyTranslations() { document.title = t("pageTitle"); const metaDesc = document.querySelector('meta[name="description"]'); if (metaDesc) metaDesc.setAttribute("content", t("pageDescription")); setText("#mainNav li:nth-of-type(1) a", t("navServices")); setText("#mainNav li:nth-of-type(2) a", t("navNumbers")); setText("#mainNav li:nth-of-type(3) a", t("navClients")); setText("#mainNav li:nth-of-type(4) a", t("navProjects")); setText("#mainNav li:nth-of-type(5) a", t("navBlog")); setText("#mainNav li:nth-of-type(6) a", t("navContact")); setText(".cta-nav", t("ctaTop")); const menuBtnLabel = document.getElementById("menuBtn"); if (menuBtnLabel) menuBtnLabel.setAttribute("aria-label", t("menuOpenLabel")); setText("#home .eyebrow", t("heroEyebrow")); setHtml("#home h1", t("heroTitle")); setText("#home .content-col > p:last-of-type", t("heroLead")); setText("#home .hero-actions a:nth-of-type(1)", t("heroPrimary")); setText("#home .hero-actions a:nth-of-type(2)", t("heroSecondary")); setText("#services .section-head h2", t("servicesTitle")); setText("#services .section-head p", t("servicesLead")); setText("#services .card:nth-of-type(1) h3", t("service1Title")); setText("#services .card:nth-of-type(1) p", t("service1Desc")); setText("#services .card:nth-of-type(1) li:nth-of-type(1)", t("service1Item1")); setText("#services .card:nth-of-type(1) li:nth-of-type(2)", t("service1Item2")); setText("#services .card:nth-of-type(2) h3", t("service2Title")); setText("#services .card:nth-of-type(2) p", t("service2Desc")); setText("#services .card:nth-of-type(2) li:nth-of-type(1)", t("service2Item1")); setText("#services .card:nth-of-type(2) li:nth-of-type(2)", t("service2Item2")); setText("#services .card:nth-of-type(3) h3", t("service3Title")); setText("#services .card:nth-of-type(3) p", t("service3Desc")); setText("#services .card:nth-of-type(3) li:nth-of-type(1)", t("service3Item1")); setText("#services .card:nth-of-type(3) li:nth-of-type(2)", t("service3Item2")); setText("#numbers .section-head h2", t("numbersTitle")); setText("#numbers .section-head p", t("numbersLead")); setText("#numbers .stat:nth-of-type(1) span", t("statEmployees")); setText("#numbers .stat:nth-of-type(2) span", t("statClients")); setText("#numbers .stat:nth-of-type(3) span", t("statYears")); setText("#numbers .stat:nth-of-type(4) span", t("statOffices")); setText("#clients .section-head h2", t("clientsTitle")); setText("#clients .section-head p", t("clientsLead")); setText("#clients .logo-item:nth-of-type(1)", t("client1")); setText("#clients .logo-item:nth-of-type(2)", t("client2")); setText("#clients .logo-item:nth-of-type(3)", t("client3")); setText("#clients .logo-item:nth-of-type(4)", t("client4")); setText("#clients .logo-item:nth-of-type(5)", t("client5")); setText("#clients .logo-item:nth-of-type(6)", t("client6")); setText("#projects .section-head h2", t("projectsTitle")); setText("#projects .section-head p", t("projectsLead")); setText("#projectsList .project-card h3", t("projectsLoadingTitle")); setText("#projectsList .project-card p", t("projectsLoadingDesc")); setText("#blog .section-head h2", t("blogTitle")); setText("#blog .section-head p", t("blogLead")); setText("#blog .blog-item:nth-of-type(1) a", t("article1Title")); setText("#blog .blog-item:nth-of-type(1) p", t("article1Desc")); setText("#blog .blog-item:nth-of-type(2) a", t("article2Title")); setText("#blog .blog-item:nth-of-type(2) p", t("article2Desc")); setText("#contact h3", t("contactTitle")); setText("#contact .muted", t("contactLead")); setText("#contact .contact-row:nth-of-type(1) small", t("contactLocationLabel")); setText("#contact .contact-row:nth-of-type(1) span", t("contactLocationValue")); setText("#contact .contact-row:nth-of-type(2) small", t("contactPhoneLabel")); setText("#contact .contact-row:nth-of-type(3) small", t("contactEmailLabel")); setText("#contact a[data-walkthrough-trigger='true']", t("contactPrimary")); setText("#startWalkthrough", t("contactWalkthrough")); setText(".footer-branding p", t("footerCopy")); setText(".footer-links p:nth-of-type(1)", t("footerHome")); setText(".footer-links p:nth-of-type(2)", t("footerMarket")); setText(".footer-links p:nth-of-type(3)", t("footerBlog")); setText(".footer-links p:nth-of-type(4)", t("footerContact")); setText("#walkthroughTitle", t("walkTitle")); const closeBtn = document.getElementById("walkthroughClose"); if (closeBtn) closeBtn.setAttribute("aria-label", t("walkClose")); const walkSteps = document.querySelectorAll(".walkthrough-step"); if (walkSteps[0]) { const h4 = walkSteps[0].querySelector("h4"); const p = walkSteps[0].querySelector("p"); if (h4) h4.textContent = t("walkStep1Title"); if (p) p.textContent = t("walkStep1Desc"); } if (walkSteps[1]) { const h4 = walkSteps[1].querySelector("h4"); const p = walkSteps[1].querySelector("p"); if (h4) h4.textContent = t("walkStep2Title"); if (p) p.textContent = t("walkStep2Desc"); } if (walkSteps[2]) { const h4 = walkSteps[2].querySelector("h4"); const p = walkSteps[2].querySelector("p"); if (h4) h4.textContent = t("walkStep3Title"); if (p) p.textContent = t("walkStep3Desc"); } setText("#walkthroughDismiss", t("walkDismiss")); setText(".walkthrough-actions a", t("walkContinue")); } applyDocumentLanguage(currentLang); applyTranslations(); // Mobile Menu const menuBtn = document.getElementById("menuBtn"); const navWrap = document.getElementById("mainNavWrap"); const nav = document.getElementById("mainNav"); const menuHighlight = document.getElementById("menuHighlight"); const navLinks = Array.from(nav.querySelectorAll('a[href^="#"]')); const langArBtn = document.getElementById("langAr"); const langEnBtn = document.getElementById("langEn"); let highlightXTo = null; let highlightWTo = null; function switchLanguage(lang) { applyDocumentLanguage(lang); applyTranslations(); loadProjects(); const active = nav.querySelector("a.is-active") || navLinks[0]; moveMenuHighlight(active); // Animate the globe: 360° spin + shift to the opposite side if (window.globeGroup && window.targetY_Bahrain !== undefined && typeof gsap !== 'undefined') { window.isRTL = document.documentElement.dir === 'rtl'; const isMobile = window.innerWidth <= 980; const newX = isMobile ? 0 : (window.isRTL ? -7 : 7); const duration = 0.9; // Toggle rotation direction for back-and-forth motion globeRotationOffset = (globeRotationOffset === 0) ? (Math.PI * 2) : 0; // Create a GSAP timeline for synchronized rotation + translation const tl = gsap.timeline({ onComplete: function() { if (typeof ScrollTrigger !== 'undefined') { ScrollTrigger.refresh(); } } }); // Animate rotation: spin forward or backward depending on direction tl.to(window.globeGroup.rotation, { y: window.targetY_Bahrain + globeRotationOffset, duration: duration, ease: "power2.inOut" }, 0); // Animate position shift to the opposite side simultaneously tl.to(window.globeGroup.position, { x: newX, duration: duration, ease: "power2.inOut" }, 0); } } if (langArBtn) { langArBtn.addEventListener("click", function () { switchLanguage("ar"); }); } if (langEnBtn) { langEnBtn.addEventListener("click", function () { switchLanguage("en"); }); } function setActiveNavLink(link) { navLinks.forEach(function (item) { item.classList.toggle("is-active", item === link); }); } function setupHighlightAnimation() { if (!menuHighlight || typeof gsap === "undefined") return; highlightXTo = gsap.quickTo(menuHighlight, "x", { duration: 0.38, ease: "power3.out" }); highlightWTo = gsap.quickTo(menuHighlight, "width", { duration: 0.34, ease: "power2.out" }); } setupHighlightAnimation(); function moveMenuHighlight(link) { if (!menuHighlight || !link || !navWrap || window.innerWidth <= 980) return; const navRect = navWrap.getBoundingClientRect(); const linkRect = link.getBoundingClientRect(); const x = linkRect.left - navRect.left; navWrap.classList.add("has-highlight"); if (highlightXTo && highlightWTo) { highlightXTo(x); highlightWTo(linkRect.width); } else if (typeof gsap !== "undefined") { gsap.to(menuHighlight, { x: x, width: linkRect.width, duration: 0.34, ease: "power2.out" }); } else { menuHighlight.style.transform = "translateX(" + x + "px)"; menuHighlight.style.width = linkRect.width + "px"; } } function toggleMenu() { const open = nav.classList.toggle("is-open"); menuBtn.setAttribute("aria-expanded", String(open)); document.body.classList.toggle("menu-open", open); } menuBtn.addEventListener("click", toggleMenu); nav.querySelectorAll("a").forEach(function (anchor) { anchor.addEventListener("click", function () { setActiveNavLink(anchor); moveMenuHighlight(anchor); if (nav.classList.contains("is-open")) { toggleMenu(); } }); anchor.addEventListener("mouseenter", function () { moveMenuHighlight(anchor); }); anchor.addEventListener("focus", function () { moveMenuHighlight(anchor); }); }); // Intersection Observer Reveal const revealObserver = new IntersectionObserver( function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { entry.target.classList.add("is-visible"); revealObserver.unobserve(entry.target); } }); }, { threshold: 0.15 } ); document.querySelectorAll(".reveal").forEach(function (node) { revealObserver.observe(node); }); // Scroll-based active navigation and highlighter alignment const sectionMap = navLinks .map(function (link) { const selector = link.getAttribute("href"); const section = selector ? document.querySelector(selector) : null; return section ? { link: link, section: section } : null; }) .filter(Boolean); let rafScheduled = false; function updateActiveLinkFromViewport() { if (!sectionMap.length) return; const viewportFocus = window.innerHeight * 0.36; let winner = sectionMap[0]; let minDistance = Number.POSITIVE_INFINITY; sectionMap.forEach(function (item) { const rect = item.section.getBoundingClientRect(); const sectionMid = rect.top + (rect.height * 0.5); const distance = Math.abs(sectionMid - viewportFocus); if (distance < minDistance) { minDistance = distance; winner = item; } }); if (winner) { setActiveNavLink(winner.link); moveMenuHighlight(winner.link); } } function queueActiveUpdate() { if (rafScheduled) return; rafScheduled = true; requestAnimationFrame(function () { rafScheduled = false; updateActiveLinkFromViewport(); }); } window.addEventListener("scroll", queueActiveUpdate, { passive: true }); window.addEventListener("hashchange", queueActiveUpdate); const initialLink = navLinks[0]; if (initialLink) { setActiveNavLink(initialLink); moveMenuHighlight(initialLink); } queueActiveUpdate(); window.addEventListener("resize", function () { const active = nav.querySelector("a.is-active") || navLinks[0]; if (window.innerWidth <= 980) { navWrap.classList.remove("has-highlight"); return; } moveMenuHighlight(active); queueActiveUpdate(); }); if (typeof gsap !== "undefined" && typeof ScrollTrigger !== "undefined") { gsap.registerPlugin(ScrollTrigger); gsap.fromTo( document.documentElement, { "--logo-scale": 1 }, { "--logo-scale": 0.86, ease: "none", scrollTrigger: { trigger: "main", start: "top top", end: "top+=260 top", scrub: true } } ); } // Number Counting Animation const formatStat = function (value) { if (value >= 1000000) return "+" + Math.floor(value / 1000000) + "M"; if (value >= 1000) return value.toLocaleString(); return String(value); }; const statsObserver = new IntersectionObserver( function (entries) { entries.forEach(function (entry) { if (!entry.isIntersecting) return; const el = entry.target; const target = Number(el.getAttribute("data-target")); const start = performance.now(); const duration = 1300; function update(now) { const progress = Math.min((now - start) / duration, 1); const eased = 1 - Math.pow(1 - progress, 3); const current = Math.floor(target * eased); el.textContent = formatStat(current); if (progress < 1) { requestAnimationFrame(update); } else { el.textContent = formatStat(target); } } requestAnimationFrame(update); statsObserver.unobserve(el); }); }, { threshold: 0.35 } ); document.querySelectorAll(".stat strong[data-target]").forEach(function (el) { statsObserver.observe(el); }); // Walkthrough modal const walkthroughOverlay = document.getElementById("walkthroughOverlay"); const startWalkthrough = document.getElementById("startWalkthrough"); const walkthroughClose = document.getElementById("walkthroughClose"); const walkthroughDismiss = document.getElementById("walkthroughDismiss"); const walkthroughTriggers = Array.from(document.querySelectorAll("[data-walkthrough-trigger='true']")); function closeWalkthrough() { if (!walkthroughOverlay) return; walkthroughOverlay.classList.remove("is-open"); walkthroughOverlay.setAttribute("aria-hidden", "true"); document.body.classList.remove("menu-open"); } function openWalkthrough() { if (!walkthroughOverlay) return; walkthroughOverlay.classList.add("is-open"); walkthroughOverlay.setAttribute("aria-hidden", "false"); document.body.classList.add("menu-open"); } if (startWalkthrough) { startWalkthrough.addEventListener("click", openWalkthrough); } walkthroughTriggers.forEach(function (trigger) { trigger.addEventListener("click", function (event) { event.preventDefault(); openWalkthrough(); }); }); if (walkthroughClose) { walkthroughClose.addEventListener("click", closeWalkthrough); } if (walkthroughDismiss) { walkthroughDismiss.addEventListener("click", closeWalkthrough); } if (walkthroughOverlay) { walkthroughOverlay.addEventListener("click", function (event) { if (event.target === walkthroughOverlay) { closeWalkthrough(); } }); } document.addEventListener("keydown", function (event) { if (event.key === "Escape") { closeWalkthrough(); } }); // Dynamic projects from markdown manifest and files const projectsList = document.getElementById("projectsList"); function escapeHtml(text) { return String(text) .replace(/&/g, "&") .replace(//g, ">") .replace(/\"/g, """) .replace(/'/g, "'"); } async function fetchFirstAvailable(paths) { for (let i = 0; i < paths.length; i++) { try { const response = await fetch(paths[i], { cache: "no-store" }); if (response.ok) { return { content: await response.text(), path: paths[i] }; } } catch (error) { // Try next path candidate. } } return null; } function toAbsolutePath(basePath, relativePath) { if (/^https?:\/\//i.test(relativePath)) return relativePath; if (relativePath.charAt(0) === "/") return relativePath; const baseParts = basePath.split("/"); baseParts.pop(); const relParts = relativePath.split("/"); relParts.forEach(function (part) { if (!part || part === ".") return; if (part === "..") { if (baseParts.length > 1) baseParts.pop(); return; } baseParts.push(part); }); return baseParts.join("/"); } function parseManifestLinks(markdown) { const links = []; const linkRegex = /\[([^\]]+)\]\(([^)]+\.md)\)/gim; let match; while ((match = linkRegex.exec(markdown))) { links.push({ title: match[1].trim(), file: match[2].trim() }); } return links; } function summarizeMarkdown(markdown, fallbackTitle) { const heading = markdown.match(/^#\s+(.+)$/m); const title = (heading && heading[1] ? heading[1] : fallbackTitle || t("projectDefaultTitle")).trim(); const paragraphMatch = markdown .replace(/^#.*$/gm, "") .replace(/^\s*[-*].*$/gm, "") .split(/\n\s*\n/) .map(function (part) { return part.trim(); }) .find(function (part) { return part.length > 24; }); const summary = paragraphMatch ? paragraphMatch.replace(/\s+/g, " ").slice(0, 180) : t("projectDefaultSummary"); return { title: title, summary: summary }; } async function loadProjects() { if (!projectsList) return; const manifestResult = await fetchFirstAvailable([ "pages/PROJECTS.md", "../pages/PROJECTS.md", "/pages/PROJECTS.md" ]); if (!manifestResult) { projectsList.innerHTML = "

" + escapeHtml(t("projectsEmptyTitle")) + "

" + escapeHtml(t("projectsEmptyDesc")) + "

"; return; } const projectLinks = parseManifestLinks(manifestResult.content); if (projectLinks.length === 0) { const fallback = summarizeMarkdown(manifestResult.content, t("projectsFallbackTitle")); projectsList.innerHTML = "

" + escapeHtml(fallback.title) + "

" + escapeHtml(fallback.summary) + "

"; return; } const cards = await Promise.all( projectLinks.slice(0, 12).map(async function (project) { const resolvedPath = toAbsolutePath(manifestResult.path, project.file); const projectResult = await fetchFirstAvailable([ resolvedPath, toAbsolutePath("/", project.file), "pages/PROJECTS/" + project.file.split("/").pop(), "../pages/PROJECTS/" + project.file.split("/").pop() ]); const parsed = summarizeMarkdown( projectResult ? projectResult.content : "", project.title || t("projectDefaultTitle") ); const href = projectResult ? resolvedPath : manifestResult.path; return "
" + "

" + escapeHtml(parsed.title) + "

" + "

" + escapeHtml(parsed.summary) + "

" + "" + escapeHtml(t("projectsReadMore")) + "" + "
"; }) ); projectsList.innerHTML = cards.join(""); projectsList.querySelectorAll(".reveal").forEach(function (node) { revealObserver.observe(node); }); } loadProjects(); })(); // ----------------------------------------------------- // 3D Globe with GSAP ScrollTrigger Integration // ----------------------------------------------------- window.onload = function () { const container = document.getElementById('globe-canvas'); if (!container || typeof THREE === 'undefined' || typeof gsap === 'undefined') return; gsap.registerPlugin(ScrollTrigger); let width = container.clientWidth || window.innerWidth; let height = container.clientHeight || window.innerHeight; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000); camera.position.set(0, 0, 28); const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }); renderer.setSize(width, height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); container.appendChild(renderer.domElement); // Lighting const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambientLight); const sunLight = new THREE.DirectionalLight(0xffffff, 1.1); sunLight.position.set(200, 100, 200); scene.add(sunLight); const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.4); hemiLight.position.set(0, 100, 0); scene.add(hemiLight); // Globe Group const globeGroup = new THREE.Group(); scene.add(globeGroup); const radius = 10; const segments = 64; const globeGeometry = new THREE.SphereGeometry(radius, segments, segments); const textureLoader = new THREE.TextureLoader(); const earthMap = textureLoader.load('https://unpkg.com/three-globe/example/img/earth-blue-marble.jpg'); const bumpMap = textureLoader.load('https://unpkg.com/three-globe/example/img/earth-topology.png'); const specularMap = textureLoader.load('https://unpkg.com/three-globe/example/img/earth-water.png'); const solidMaterial = new THREE.MeshPhongMaterial({ map: earthMap, bumpMap: bumpMap, bumpScale: 0.08, specularMap: specularMap, specular: new THREE.Color(0x222222), shininess: 8 }); const solidGlobe = new THREE.Mesh(globeGeometry, solidMaterial); solidGlobe.rotation.set(0, 0, 0); globeGroup.add(solidGlobe); // Coordinate utility function latLongToVector3(lat, lon, radius, heightOffset = 0) { const latRad = lat * (Math.PI / 180); const lonRad = lon * (Math.PI / 180); const r = radius + heightOffset; const x = r * Math.cos(latRad) * Math.cos(lonRad); const y = r * Math.sin(latRad); const z = -r * Math.cos(latRad) * Math.sin(lonRad); return new THREE.Vector3(x, y, z); } // Bahrain Marker const bahrainLat = 26.0667; const bahrainLon = 50.5577; const targetY_Bahrain = (-90 - bahrainLon) * (Math.PI / 180); // Exact longitude facing const bahrainPos = latLongToVector3(bahrainLat, bahrainLon, radius, 0.05); const markerGeo = new THREE.SphereGeometry(0.06, 16, 16); const markerMat = new THREE.MeshBasicMaterial({ color: 0xff7f45 }); const marker = new THREE.Mesh(markerGeo, markerMat); marker.position.copy(bahrainPos); globeGroup.add(marker); const ringGeo = new THREE.RingGeometry(0.12, 0.20, 32); const ringMat = new THREE.MeshBasicMaterial({ color: 0xf16522, side: THREE.DoubleSide, transparent: true, opacity: 0.9 }); const ring = new THREE.Mesh(ringGeo, ringMat); ring.position.copy(bahrainPos); ring.lookAt(new THREE.Vector3(0, 0, 0)); globeGroup.add(ring); const beaconPoints = [bahrainPos, bahrainPos.clone().multiplyScalar(1.15)]; const beaconGeo = new THREE.BufferGeometry().setFromPoints(beaconPoints); const beaconMat = new THREE.LineBasicMaterial({ color: 0xff7f45, transparent: true, opacity: 0.7 }); const beacon = new THREE.Line(beaconGeo, beaconMat); globeGroup.add(beacon); // Flight Paths const flightCurves = []; const destinations = [ { lat: 40.7128, lon: -74.0060 }, { lat: 51.5074, lon: -0.1278 }, { lat: 35.6895, lon: 139.6917 }, { lat: 1.3521, lon: 103.8198 }, { lat: -33.8688,lon: 151.2093 }, { lat: -23.5505,lon: -46.6333 }, { lat: 25.2048, lon: 55.2708 }, { lat: 48.8566, lon: 2.3522 }, { lat: 37.7749, lon: -122.4194 },{ lat: -33.9249,lon: 18.4241 }, { lat: 19.0760, lon: 72.8777 }, { lat: 55.7558, lon: 37.6173 }, { lat: 39.9042, lon: 116.4074 }, { lat: 52.5200, lon: 13.4050 }, { lat: -1.2921, lon: 36.8219 } ]; destinations.forEach(dest => { const destPos = latLongToVector3(dest.lat, dest.lon, radius, 0.05); const distance = bahrainPos.distanceTo(destPos); const lift = radius + (distance * 0.25); const mid1 = bahrainPos.clone().lerp(destPos, 0.33).normalize().multiplyScalar(lift); const mid2 = bahrainPos.clone().lerp(destPos, 0.66).normalize().multiplyScalar(lift); const curve = new THREE.CubicBezierCurve3(bahrainPos, mid1, mid2, destPos); flightCurves.push(curve); const points = curve.getPoints(50); const lineGeo = new THREE.BufferGeometry().setFromPoints(points); const lineMat = new THREE.LineBasicMaterial({ color: 0x28b8f7, transparent: true, opacity: 0.65 }); globeGroup.add(new THREE.Line(lineGeo, lineMat)); const dotGeo = new THREE.SphereGeometry(0.12, 8, 8); const dotMat = new THREE.MeshBasicMaterial({ color: 0x6be39a }); const dot = new THREE.Mesh(dotGeo, dotMat); dot.position.copy(destPos); globeGroup.add(dot); }); // Data Signals const signals = []; const numSignals = 2; const signalGeo = new THREE.SphereGeometry(0.14, 16, 16); for (let i = 0; i < numSignals; i++) { const signalMat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 1 }); const signalMesh = new THREE.Mesh(signalGeo, signalMat); signalMesh.visible = false; globeGroup.add(signalMesh); signals.push({ mesh: signalMesh, mat: signalMat, routeIndex: -1, progress: 0, speed: 0.010 + (Math.random() * 0.005), phaseOffset: Math.random() * Math.PI * 2 }); } // ----------------------------------------------------- // GSAP Layout Mapping with RTL/LTR Awareness // ----------------------------------------------------- const isMobile = window.innerWidth <= 980; let isRTL = document.documentElement.dir === 'rtl'; // For RTL (Arabic): globe always sits on the LEFT side (negative x) // For LTR (English): globe alternates left/right based on section function layoutX(x) { return isRTL ? -Math.abs(x) : x; } // Define Base State (Section 1: Hero) // Desktop: Content Left (Globe Right for LTR, Globe Left for RTL). Mobile: Centered. globeGroup.position.set(isMobile ? 0 : layoutX(7), isMobile ? 0.65 : -1, 0); globeGroup.scale.setScalar(isMobile ? 0.78 : 1.0); globeGroup.rotation.set(isMobile ? 0.18 : 0.35, targetY_Bahrain, 0); camera.position.set(0, isMobile ? 0.35 : 0, isMobile ? 30 : 28); // Expose to window so switchLanguage can access them window.globeGroup = globeGroup; window.isRTL = isRTL; window.targetY_Bahrain = targetY_Bahrain; // Create media-aware timeline let globeMM = gsap.matchMedia(); window.globeMM = globeMM; globeMM.add("(min-width: 981px)", () => { // Update direction inside callback in case language changed isRTL = document.documentElement.dir === 'rtl'; // DESKTOP: Dynamic alternating layout with Capped Zooming const tl = gsap.timeline({ scrollTrigger: { trigger: "main", start: "top top", end: "bottom bottom", scrub: 1.5 // Smooth catchup } }); // Section 2: Services (Zoom IN slightly to 120% max, Globe Left) tl.to(globeGroup.position, { x: layoutX(-6), y: 0, z: 0, ease: "power1.inOut" }, "sec2") .to(globeGroup.scale, { x: 1.2, y: 1.2, z: 1.2, ease: "power1.inOut" }, "sec2") .to(globeGroup.rotation, { x: 0.1, y: 1.2, ease: "power1.inOut" }, "sec2"); // Section 3: Numbers (Zoom OUT to 85%, Globe alternates side) tl.to(globeGroup.position, { x: layoutX(7), y: 1, z: 0, ease: "power1.inOut" }, "sec3") .to(globeGroup.scale, { x: 0.85, y: 0.85, z: 0.85, ease: "power1.inOut" }, "sec3") .to(globeGroup.rotation, { x: 0.5, y: -0.4, ease: "power1.inOut" }, "sec3"); // Section 4: Clients (Zoom moderately to 110%, Globe alternates side) tl.to(globeGroup.position, { x: layoutX(-7), y: -1, z: 0, ease: "power1.inOut" }, "sec4") .to(globeGroup.scale, { x: 1.1, y: 1.1, z: 1.1, ease: "power1.inOut" }, "sec4") .to(globeGroup.rotation, { x: -0.2, y: 2.5, ease: "power1.inOut" }, "sec4"); // Section 5: Blog & Contact (Back to original 100% scale and position, facing Bahrain) tl.to(globeGroup.position, { x: layoutX(7), y: -1, z: 0, ease: "power1.inOut" }, "sec5") .to(globeGroup.scale, { x: 1.0, y: 1.0, z: 1.0, ease: "power1.inOut" }, "sec5") .to(globeGroup.rotation, { x: 0.35, y: targetY_Bahrain + (Math.PI * 2), ease: "power1.inOut" }, "sec5"); }); globeMM.add("(max-width: 980px)", () => { // MOBILE: Stacked Layout (Globe stays central but rotates/zooms variably) const tl = gsap.timeline({ scrollTrigger: { trigger: "main", start: "top top", end: "bottom bottom", scrub: 1.5 } }); tl.to(globeGroup.position, { x: 0, y: 2, z: 0, ease: "power1.inOut" }, "sec2") .to(globeGroup.scale, { x: 0.92, y: 0.92, z: 0.92, ease: "power1.inOut" }, "sec2") .to(globeGroup.rotation, { x: 0.1, y: 1.2, ease: "power1.inOut" }, "sec2"); tl.to(globeGroup.position, { x: 0, y: -2, z: 0, ease: "power1.inOut" }, "sec3") .to(globeGroup.scale, { x: 0.7, y: 0.7, z: 0.7, ease: "power1.inOut" }, "sec3") .to(globeGroup.rotation, { x: 0.5, y: -0.4, ease: "power1.inOut" }, "sec3"); tl.to(globeGroup.position, { x: 0, y: 1, z: 0, ease: "power1.inOut" }, "sec4") .to(globeGroup.scale, { x: 0.86, y: 0.86, z: 0.86, ease: "power1.inOut" }, "sec4") .to(globeGroup.rotation, { x: -0.2, y: 2.5, ease: "power1.inOut" }, "sec4"); // Return to Bahrain-focused state in frame. tl.to(globeGroup.position, { x: 0, y: 0.65, z: 0, ease: "power1.inOut" }, "sec5") .to(globeGroup.scale, { x: 0.78, y: 0.78, z: 0.78, ease: "power1.inOut" }, "sec5") .to(globeGroup.rotation, { x: 0.18, y: targetY_Bahrain + (Math.PI * 2), ease: "power1.inOut" }, "sec5"); }); // Handle Resize window.addEventListener('resize', () => { const w = window.innerWidth; const h = window.innerHeight; camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h); }); // Animation Loop let time = 0; function animate() { requestAnimationFrame(animate); // Constant idle animations independent of scroll time += 0.05; ring.scale.setScalar(1 + Math.sin(time) * 0.2); ringMat.opacity = 0.5 + Math.sin(time) * 0.3; if (flightCurves.length > 0) { signals.forEach(signal => { if (signal.routeIndex === -1) { if (Math.random() > 0.05) return; signal.routeIndex = Math.floor(Math.random() * flightCurves.length); signal.progress = 0; signal.mesh.visible = true; } signal.progress += signal.speed; if (signal.progress >= 1) { signal.mesh.visible = false; signal.routeIndex = -1; } else { const pos = flightCurves[signal.routeIndex].getPoint(signal.progress); signal.mesh.position.copy(pos); signal.mat.opacity = 0.4 + Math.sin(time * 12 + signal.phaseOffset) * 0.6; signal.mesh.scale.setScalar(1 + Math.sin(time * 8 + signal.phaseOffset) * 0.2); } }); } renderer.render(scene, camera); } animate(); // Start loop // Hide the loading overlay once everything is fully loaded const loader = document.getElementById('loaderOverlay'); if (loader) { setTimeout(function() { loader.classList.add('is-hidden'); }, 600); } }; // ----------------------------------------------------- // Crazy Multi-Step Form Logic // ----------------------------------------------------- (function() { const form = document.getElementById('crazyForm'); if (!form) return; let currentStep = 1; const totalSteps = 4; const progressFill = document.getElementById('progressFill'); const steps = form.querySelectorAll('.step'); const formSteps = form.querySelectorAll('.form-step'); // Selection state const selections = { type: null, budget: null, timeline: null }; function updateProgress(step) { const progress = (step / totalSteps) * 100; progressFill.style.width = progress + '%'; steps.forEach((s, index) => { const stepNum = index + 1; s.classList.toggle('active', stepNum <= step); }); } function showStep(step) { formSteps.forEach(s => s.classList.remove('active')); const targetStep = form.querySelector(`.form-step[data-step="${step}"]`); if (targetStep) { targetStep.classList.add('active'); // GSAP animation if (typeof gsap !== 'undefined') { gsap.fromTo(targetStep, { opacity: 0, y: 30, scale: 0.95 }, { opacity: 1, y: 0, scale: 1, duration: 0.6, ease: "back.out(1.7)" } ); } } updateProgress(step); currentStep = step; } // Project type selection const typeCards = form.querySelectorAll('.type-card'); typeCards.forEach(card => { card.addEventListener('click', function() { typeCards.forEach(c => c.style.borderColor = 'rgba(255, 255, 255, 0.15)'); this.style.borderColor = 'var(--brand)'; this.style.boxShadow = '0 0 30px rgba(255, 127, 69, 0.6)'; selections.type = this.dataset.type; // Auto advance after short delay setTimeout(() => showStep(2), 400); }); // Haptic-like animation on hover card.addEventListener('mouseenter', function() { if (typeof gsap !== 'undefined') { gsap.to(this, { scale: 1.05, duration: 0.3, ease: "power2.out" }); } }); card.addEventListener('mouseleave', function() { if (typeof gsap !== 'undefined') { gsap.to(this, { scale: 1, duration: 0.3, ease: "power2.out" }); } }); }); // Budget selection const budgetCards = form.querySelectorAll('.budget-card'); budgetCards.forEach(card => { card.addEventListener('click', function() { budgetCards.forEach(c => c.style.borderColor = 'rgba(255, 255, 255, 0.15)'); this.style.borderColor = 'var(--brand)'; this.style.boxShadow = '0 0 30px rgba(255, 127, 69, 0.6)'; selections.budget = this.dataset.budget; setTimeout(() => showStep(3), 400); }); card.addEventListener('mouseenter', function() { if (typeof gsap !== 'undefined') { gsap.to(this, { scale: 1.05, duration: 0.3, ease: "power2.out" }); } }); card.addEventListener('mouseleave', function() { if (typeof gsap !== 'undefined') { gsap.to(this, { scale: 1, duration: 0.3, ease: "power2.out" }); } }); }); // Timeline selection const timelineCards = form.querySelectorAll('.timeline-card'); timelineCards.forEach(card => { card.addEventListener('click', function() { timelineCards.forEach(c => c.style.borderColor = 'rgba(255, 255, 255, 0.15)'); this.style.borderColor = 'var(--brand)'; this.style.boxShadow = '0 0 30px rgba(255, 127, 69, 0.6)'; selections.timeline = this.dataset.timeline; setTimeout(() => showStep(4), 400); }); card.addEventListener('mouseenter', function() { if (typeof gsap !== 'undefined') { gsap.to(this, { scale: 1.05, duration: 0.3, ease: "power2.out" }); } }); card.addEventListener('mouseleave', function() { if (typeof gsap !== 'undefined') { gsap.to(this, { scale: 1, duration: 0.3, ease: "power2.out" }); } }); }); // Form submission const projectForm = document.getElementById('projectForm'); if (projectForm) { projectForm.addEventListener('submit', function(e) { e.preventDefault(); const submitBtn = this.querySelector('.submit-btn'); submitBtn.classList.add('loading'); // Simulate API call setTimeout(() => { submitBtn.classList.remove('loading'); showStep(5); // Show success // Confetti effect if (typeof gsap !== 'undefined') { const successIcon = form.querySelector('.success-icon'); if (successIcon) { gsap.fromTo(successIcon, { scale: 0, rotation: -180 }, { scale: 1, rotation: 0, duration: 0.8, ease: "elastic.out(1, 0.3)" } ); } } }, 1500); }); } // Input focus animations const inputs = form.querySelectorAll('input, textarea'); inputs.forEach(input => { input.addEventListener('focus', function() { if (typeof gsap !== 'undefined') { gsap.to(this, { scale: 1.02, duration: 0.3, ease: "power2.out" }); } }); input.addEventListener('blur', function() { if (typeof gsap !== 'undefined') { gsap.to(this, { scale: 1, duration: 0.3, ease: "power2.out" }); } }); }); // Initialize first step showStep(1); })();