1// Version: 2.1 2console.log('AI System Agent Landing Page v2.1 Loaded'); 3 4document.addEventListener('DOMContentLoaded', () => { 5 6 // Animation Observer 7 const observerOptions = { 8 root: null, 9 rootMargin: '0px', 10 threshold: 0.1 11 }; 12 13 const observer = new IntersectionObserver((entries, observer) => { 14 entries.forEach(entry => { 15 if (entry.isIntersecting) { 16 entry.target.classList.add('animate-fade-in-up'); 17 entry.target.style.opacity = '1'; 18 observer.unobserve(entry.target); 19 } 20 }); 21 }, observerOptions); 22 23 document.querySelectorAll('.animate-on-scroll').forEach(el => { 24 el.style.opacity = '0'; 25 observer.observe(el); 26 }); 27 28 // Mobile Menu Toggle 29 const btn = document.getElementById('mobile-menu-btn'); 30 const menu = document.getElementById('mobile-menu'); 31 32 if (btn && menu) { 33 btn.addEventListener('click', () => { 34 menu.classList.toggle('hidden'); 35 }); 36 } 37 38 // Smooth Scroll for anchor links 39 document.querySelectorAll('a[href^="#"]').forEach(anchor => { 40 anchor.addEventListener('click', function (e) { 41 e.preventDefault(); 42 menu?.classList.add('hidden'); // Close mobile menu if open 43 44 const href = this.getAttribute('href'); 45 if (href === '#') { 46 window.scrollTo({ 47 top: 0, 48 behavior: 'smooth' 49 }); 50 return; 51 } 52 53 const targetElement = document.querySelector(href); 54 if (targetElement) { 55 targetElement.scrollIntoView({ 56 behavior: 'smooth' 57 }); 58 } 59 }); 60 }); 61 62 // Navbar scroll effect 63 const navbar = document.getElementById('navbar'); 64 window.addEventListener('scroll', () => { 65 if (window.scrollY > 50) { 66 navbar.classList.add('shadow-lg', 'bg-dark-bg/95'); 67 } else { 68 navbar.classList.remove('shadow-lg', 'bg-dark-bg/95'); 69 } 70 }); 71 72 // Google Consent Mode v2 Logic 73 initCookieConsent(); 74 75 // Interactive Demo Animation 76 setupDemoAnimation(); 77 78 // Forward marketing attribution (UTM tags / referrer) to the app so we can 79 // track which campaigns and landing-page visits convert into sign-ups. 80 forwardAttributionToAppLinks(); 81}); 82 83function initCookieConsent() { 84 const banner = document.getElementById('cookie-banner'); 85 const btnAcceptAll = document.getElementById('cookie-accept'); 86 const btnDeclineAll = document.getElementById('cookie-decline'); 87 const btnPreferences = document.getElementById('cookie-preferences'); 88 89 const modal = document.getElementById('cookie-modal'); 90 const modalContent = document.getElementById('cookie-modal-content'); 91 const btnCloseModal = document.getElementById('close-cookie-modal'); 92 const btnSavePreferences = document.getElementById('save-cookie-preferences'); 93 94 const toggleAnalytics = document.getElementById('toggle-analytics'); 95 const toggleMarketing = document.getElementById('toggle-marketing'); 96 97 if (!banner) return; 98 99 // Helper: Hide banner 100 const hideBanner = () => { 101 banner.classList.add('translate-y-full'); 102 setTimeout(() => { banner.style.display = 'none'; }, 300); 103 }; 104 105 // Helper: Show modal 106 const showModal = () => { 107 modal.classList.remove('opacity-0', 'pointer-events-none'); 108 modalContent.classList.remove('scale-95'); 109 }; 110 111 // Helper: Hide modal 112 const hideModal = () => { 113 modal.classList.add('opacity-0', 'pointer-events-none'); 114 modalContent.classList.add('scale-95'); 115 }; 116 117 // Helper: Update Google Consent Mode via gtag 118 const updateGtagConsent = (analyticsGranted, adGranted) => { 119 if (typeof gtag === 'function') { 120 gtag('consent', 'update', { 121 'analytics_storage': analyticsGranted ? 'granted' : 'denied', 122 'ad_storage': adGranted ? 'granted' : 'denied', 123 'ad_user_data': adGranted ? 'granted' : 'denied', 124 'ad_personalization': adGranted ? 'granted' : 'denied' 125 }); 126 } 127 }; 128 129 // Check existing consent 130 const storedConsent = localStorage.getItem('consentMode'); 131 132 if (!storedConsent) { 133 // Show banner after delay 134 setTimeout(() => { 135 banner.style.display = 'block'; 136 banner.offsetHeight; // force reflow 137 banner.classList.remove('translate-y-full'); 138 }, 1000); 139 } else { 140 // Apply stored consent immediately 141 try { 142 const consentObj = JSON.parse(storedConsent);
143 updateGtagConsent(consentObj.analytics, consentObj.marketing); 144 145 // Sync toggle UI with stored state 146 if (toggleAnalytics) toggleAnalytics.checked = consentObj.analytics; 147 if (toggleMarketing) toggleMarketing.checked = consentObj.marketing; 148 } catch(e) { 149 console.error("Error parsing consent data", e); 150 } 151 } 152 153 // Accept All Click 154 if (btnAcceptAll) { 155 btnAcceptAll.addEventListener('click', () => { 156 const state = { analytics: true, marketing: true }; 157 localStorage.setItem('consentMode', JSON.stringify(state)); 158 updateGtagConsent(true, true); 159 160 if (toggleAnalytics) toggleAnalytics.checked = true; 161 if (toggleMarketing) toggleMarketing.checked = true; 162 163 hideBanner(); 164 }); 165 } 166 167 // Decline All Click 168 if (btnDeclineAll) { 169 btnDeclineAll.addEventListener('click', () => { 170 const state = { analytics: false, marketing: false }; 171 localStorage.setItem('consentMode', JSON.stringify(state)); 172 updateGtagConsent(false, false); 173 174 if (toggleAnalytics) toggleAnalytics.checked = false; 175 if (toggleMarketing) toggleMarketing.checked = false; 176 177 hideBanner(); 178 }); 179 } 180 181 // Preferences Click 182 if (btnPreferences) { 183 btnPreferences.addEventListener('click', () => { 184 showModal(); 185 }); 186 } 187 188 // Modal Close 189 if (btnCloseModal) { 190 btnCloseModal.addEventListener('click', hideModal); 191 } 192 193 // Click outside to close modal 194 if (modal) { 195 modal.addEventListener('click', (e) => { 196 if (e.target === modal) { 197 hideModal(); 198 } 199 }); 200 } 201 202 // Save Preferences Click 203 if (btnSavePreferences) { 204 btnSavePreferences.addEventListener('click', () => { 205 const state = { 206 analytics: toggleAnalytics ? toggleAnalytics.checked : false, 207 marketing: toggleMarketing ? toggleMarketing.checked : false 208 }; 209 210 localStorage.setItem('consentMode', JSON.stringify(state)); 211 updateGtagConsent(state.analytics, state.marketing); 212 213 hideModal(); 214 hideBanner(); 215 }); 216 } 217} 218 219function setupDemoAnimation() { 220 const inputField = document.getElementById('demo-input'); 221 const loadingDiv = document.getElementById('demo-loading'); 222 const planContainer = document.getElementById('demo-plan-container'); 223 const planContent = document.getElementById('demo-plan-content'); 224 const sendBtn = document.getElementById('demo-send-btn'); 225 const toast = document.getElementById('demo-status-toast'); 226 227 const tabLinux = document.getElementById('tab-linux'); 228 const tabWindows = document.getElementById('tab-windows'); 229 230 if (!inputField || !loadingDiv || !planContainer || !planContent) return; 231 232 let currentOS = 'linux'; 233 let typeTimeout = null; 234 let simTimeout = null; 235 let charIndex = 0; 236 237 const data = { 238 linux: { 239 text: "Install and configure nginx with SSL using Certbot for example.com with root directory /srv/example.com", 240 html: ` 241 <!-- Analysis --> 242 <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> 243 <div class="bg-gray-800/50 p-3 rounded border border-gray-700"> 244 <h4 class="text-xs font-bold text-gray-400 uppercase mb-2">Problem Description</h4> 245 <p class="text-sm text-gray-300">Install and configure nginx with SSL using Certbot for example.com with root directory /srv/example.com</p> 246 </div> 247 <div class="bg-gray-800/50 p-3 rounded border border-gray-700"> 248 <h4 class="text-xs font-bold text-gray-400 uppercase mb-2">AI Reasoning</h4> 249 <p class="text-sm text-gray-300">The plan involves installing nginx, setting up the web root directory, configuring a server block, securing with Let's Encrypt SSL via certbot, and adjusting firewall settings.</p> 250 </div> 251 </div> 252 253 <!-- Recommendations --> 254 <div class="bg-blue-900/10 border border-blue-900/30 p-3 rounded"> 255 <h4 class="text-xs font-bold text-blue-400 uppercase mb-1">Recommendations</h4> 256 <ul class="list-disc list-inside text-xs text-gray-300 space-y-1"> 257 <li>Ensure DNS records point example.com to your server's IP before starting</li> 258 <li>Consider taking a snapshot or backup before making system changes</li> 259 <li>Verify SSL configuration at https://www.ssllabs.com/ssltest/ after completion</li> 260 </ul> 261 </div> 262 263 <!-- Command List --> 264 <div class="space-y-3"> 265 <h4 class="text-xs font-bold text-gray-400 uppercase border-b border-gray-700 pb-2">Command Plan</h4> 266 267 <!-- Cmd 1 --> 268 <div class="group flex items-start gap-3 p-3 rounded bg-gray-800 hover:bg-gray-750 transition border border-gray-700"> 269 <input type="checkbox" checked class="mt-1 rounded border-gray-600 bg-gray-700 text-neon-blue focus:ring-offset-gray-900"> 270 <div class="flex-1"> 271 <div class="flex justify-between items-start"> 272 <code class="text-sm font-bold text-neon-green">sudo dnf install nginx</code>
273 <span class="text-[10px] px-2 py-0.5 rounded bg-green-900/30 text-green-400 border border-green-900">Low Risk</span> 274 </div> 275 <p class="text-xs text-gray-400 mt-1">Install nginx web server from default repositories</p> 276 </div> 277 </div> 278 279 <!-- Cmd 2 --> 280 <div class="group flex items-start gap-3 p-3 rounded bg-gray-800 hover:bg-gray-750 transition border border-gray-700"> 281 <input type="checkbox" checked class="mt-1 rounded border-gray-600 bg-gray-700 text-neon-blue focus:ring-offset-gray-900"> 282 <div class="flex-1"> 283 <div class="flex justify-between items-start"> 284 <code class="text-sm font-bold text-neon-green">sudo mkdir -p /srv/example.com</code> 285 <span class="text-[10px] px-2 py-0.5 rounded bg-green-900/30 text-green-400 border border-green-900">Low Risk</span> 286 </div> 287 <p class="text-xs text-gray-400 mt-1">Create the root directory for the website</p> 288 </div> 289 </div> 290 291 <!-- Cmd 3 --> 292 <div class="group flex items-start gap-3 p-3 rounded bg-gray-800 hover:bg-gray-750 transition border border-gray-700"> 293 <input type="checkbox" checked class="mt-1 rounded border-gray-600 bg-gray-700 text-neon-blue focus:ring-offset-gray-900"> 294 <div class="flex-1"> 295 <div class="flex justify-between items-start"> 296 <code class="text-sm font-bold text-neon-green">sudo chown -R nginx:nginx /srv/example.com</code> 297 <span class="text-[10px] px-2 py-0.5 rounded bg-green-900/30 text-green-400 border border-green-900">Low Risk</span> 298 </div> 299 <p class="text-xs text-gray-400 mt-1">Set proper ownership for the web directory</p> 300 </div> 301 </div> 302 303 <!-- Cmd 4 (Highlighted) --> 304 <div class="group flex items-start gap-3 p-3 rounded bg-gray-800 hover:bg-gray-750 transition border border-gray-700 ring-1 ring-neon-purple/30"> 305 <input type="checkbox" checked class="mt-1 rounded border-gray-600 bg-gray-700 text-neon-blue focus:ring-offset-gray-900"> 306 <div class="flex-1"> 307 <div class="flex justify-between items-start"> 308 <code class="text-sm font-bold text-neon-green">sudo certbot --nginx -d example.com</code> 309 <span class="text-[10px] px-2 py-0.5 rounded bg-yellow-900/30 text-yellow-400 border border-yellow-900">Medium Risk</span> 310 </div> 311 <p class="text-xs text-gray-400 mt-1">Obtain and install SSL certificate for example.com</p> 312 </div> 313 </div> 314 315 <!-- More commands indicator --> 316 <div class="text-center text-xs text-gray-500 pt-2 italic"> 317 + 4 more commands hidden for preview... 318 </div> 319 </div> 320 ` 321 }, 322 windows: { 323 text: "Install IIS, create a website for example.com and secure it with Let's Encrypt", 324 html: ` 325 <!-- Analysis --> 326 <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> 327 <div class="bg-gray-800/50 p-3 rounded border border-gray-700"> 328 <h4 class="text-xs font-bold text-gray-400 uppercase mb-2">Problem Description</h4> 329 <p class="text-sm text-gray-300">Install IIS, create a website for example.com and secure it with Let's Encrypt</p> 330 </div> 331 <div class="bg-gray-800/50 p-3 rounded border border-gray-700"> 332 <h4 class="text-xs font-bold text-gray-400 uppercase mb-2">AI Reasoning</h4> 333 <p class="text-sm text-gray-300">The plan involves enabling the Windows Web-Server feature, creating the IIS Site bound to port 80, and running win-acme (wacs) to fetch and install the SSL certificate.</p> 334 </div> 335 </div> 336 337 <!-- Recommendations --> 338 <div class="bg-blue-900/10 border border-blue-900/30 p-3 rounded"> 339 <h4 class="text-xs font-bold text-blue-400 uppercase mb-1">Recommendations</h4> 340 <ul class="list-disc list-inside text-xs text-gray-300 space-y-1"> 341 <li>Ensure port 80 and 443 are open in Windows Firewall and external security groups</li> 342 <li>win-acme (wacs.exe) must be installed and available in the system PATH</li> 343 </ul> 344 </div> 345 346 <!-- Command List --> 347 <div class="space-y-3"> 348 <h4 class="text-xs font-bold text-gray-400 uppercase border-b border-gray-700 pb-2">Command Plan</h4> 349 350 <!-- Cmd 1 --> 351 <div class="group flex items-start gap-3 p-3 rounded bg-gray-800 hover:bg-gray-750 transition border border-gray-700"> 352 <input type="checkbox" checked class="mt-1 rounded border-gray-600 bg-gray-700 text-neon-blue focus:ring-offset-gray-900"> 353 <div class="flex-1"> 354 <div class="flex justify-between items-start"> 355 <code class="text-sm font-bold text-neon-blue">Install-WindowsFeature -name Web-Server -IncludeManagementTools</code>
356 <span class="text-[10px] px-2 py-0.5 rounded bg-green-900/30 text-green-400 border border-green-900">Low Risk</span> 357 </div> 358 <p class="text-xs text-gray-400 mt-1">Install Internet Information Services (IIS) and management tools</p> 359 </div> 360 </div> 361 362 <!-- Cmd 2 --> 363 <div class="group flex items-start gap-3 p-3 rounded bg-gray-800 hover:bg-gray-750 transition border border-gray-700"> 364 <input type="checkbox" checked class="mt-1 rounded border-gray-600 bg-gray-700 text-neon-blue focus:ring-offset-gray-900"> 365 <div class="flex-1"> 366 <div class="flex justify-between items-start"> 367 <code class="text-sm font-bold text-neon-blue">New-IISSite -Name "example.com" -BindingInformation "*:80:example.com" -PhysicalPath "C:\\inetpub\\wwwroot\\example"</code> 368 <span class="text-[10px] px-2 py-0.5 rounded bg-green-900/30 text-green-400 border border-green-900">Low Risk</span> 369 </div> 370 <p class="text-xs text-gray-400 mt-1">Create the new IIS site and configure bindings</p> 371 </div> 372 </div> 373 374 <!-- Cmd 3 (Highlighted) --> 375 <div class="group flex items-start gap-3 p-3 rounded bg-gray-800 hover:bg-gray-750 transition border border-gray-700 ring-1 ring-neon-purple/30"> 376 <input type="checkbox" checked class="mt-1 rounded border-gray-600 bg-gray-700 text-neon-blue focus:ring-offset-gray-900"> 377 <div class="flex-1"> 378 <div class="flex justify-between items-start"> 379 <code class="text-sm font-bold text-neon-blue">wacs.exe --source manual --host example.com --installation iis</code> 380 <span class="text-[10px] px-2 py-0.5 rounded bg-yellow-900/30 text-yellow-400 border border-yellow-900">Medium Risk</span> 381 </div> 382 <p class="text-xs text-gray-400 mt-1">Use win-acme to obtain an SSL certificate and configure IIS HTTPS binding</p> 383 </div> 384 </div> 385 </div> 386 ` 387 } 388 }; 389 390 function switchOS(os) { 391 if (currentOS === os) return; 392 currentOS = os; 393 394 // Update tabs styling 395 if (os === 'linux') { 396 tabLinux.className = "flex-1 py-3 text-sm font-bold text-neon-blue border-b-2 border-neon-blue transition-colors bg-gray-800 flex justify-center items-center gap-2"; 397 tabWindows.className = "flex-1 py-3 text-sm font-bold text-gray-500 hover:text-gray-300 transition-colors border-b-2 border-transparent hover:bg-gray-800/50 flex justify-center items-center gap-2"; 398 } else { 399 tabWindows.className = "flex-1 py-3 text-sm font-bold text-neon-blue border-b-2 border-neon-blue transition-colors bg-gray-800 flex justify-center items-center gap-2"; 400 tabLinux.className = "flex-1 py-3 text-sm font-bold text-gray-500 hover:text-gray-300 transition-colors border-b-2 border-transparent hover:bg-gray-800/50 flex justify-center items-center gap-2"; 401 } 402 403 // Reset animation state 404 clearTimeout(typeTimeout); 405 clearTimeout(simTimeout); 406 inputField.value = ''; 407 planContainer.classList.add('hidden'); 408 loadingDiv.classList.add('hidden'); 409 charIndex = 0; 410 411 // Start typing 412 typeWriter(); 413 } 414 415 if(tabLinux) tabLinux.onclick = () => switchOS('linux'); 416 if(tabWindows) tabWindows.onclick = () => switchOS('windows'); 417 418 let typeWriterActive = false; 419 420 // Start animation loop 421 function typeWriter() { 422 typeWriterActive = true; 423 const textToType = data[currentOS].text; 424 if (charIndex < textToType.length) { 425 inputField.value += textToType.charAt(charIndex); 426 charIndex++; 427 typeTimeout = setTimeout(typeWriter, 40); // Typing speed 428 } else { 429 // Typing done, wait then simulate execution 430 typeWriterActive = false;
431 simTimeout = setTimeout(simulateExecution, 800); 432 } 433 } 434 435 function simulateExecution() { 436 // Show loading 437 loadingDiv.classList.remove('hidden'); 438 planContainer.classList.add('hidden'); 439 440 // Simulate processing time 441 simTimeout = setTimeout(() => { 442 loadingDiv.classList.add('hidden'); 443 planContent.innerHTML = data[currentOS].html; 444 planContainer.classList.remove('hidden'); 445 446 // Add click handler to button 447 if (sendBtn) { 448 sendBtn.onclick = () => { 449 if (toast) { 450 toast.classList.remove('translate-y-20', 'opacity-0'); 451 setTimeout(() => { 452 toast.classList.add('translate-y-20', 'opacity-0'); 453 }, 3000); 454 } 455 }; 456 } 457 458 }, 1500); 459 } 460 461 // Start typing after a slight delay 462 typeTimeout = setTimeout(typeWriter, 1000); 463} 464 465/** 466 * Returns true when a referrer URL points at an EXTERNAL site (not the 467 * marketing site or the app itself). A referrer from ``sysagent.ai`` (any 468 * subdomain) or the current host is internal navigation, not a real traffic 469 * source, so it must not be forwarded as ``landing_referrer``. 470 */ 471function isExternalReferrer(referrer) { 472 if (!referrer) { 473 return false; 474 } 475 try { 476 const host = new URL(referrer).hostname.replace(/^www\./, '').toLowerCase(); 477 if (!host) { 478 return false; 479 } 480 const currentHost = (window.location.hostname || '').replace(/^www\./, '').toLowerCase(); 481 if (currentHost && host === currentHost) { 482 return false; 483 } 484 const internalDomain = 'sysagent.ai'; 485 if (host === internalDomain || host.endsWith('.' + internalDomain)) { 486 return false; 487 } 488 return true; 489 } catch (error) { 490 // Malformed referrer URL: treat as not-external (do not forward). 491 return false; 492 } 493} 494 495/** 496 * Forwards marketing attribution to the application so registrations can be 497 * traced back to the campaign / landing-page visit that drove them. 498 * 499 * Standard UTM tags and common click identifiers present on the current URL, 500 * plus the original external referrer, are appended to every call-to-action 501 * link that points at the app (the dashboard / register links). The app then 502 * captures and submits these values during sign-up. 503 */ 504function forwardAttributionToAppLinks() { 505 try { 506 const params = new URLSearchParams(window.location.search); 507 const attribution = new URLSearchParams(); 508 509 ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid', 'fbclid', 'ref'].forEach((key) => { 510 const value = params.get(key); 511 if (value) { 512 attribution.set(key, value); 513 } 514 }); 515 516 // Preserve the original referrer only when it is EXTERNAL: once the 517 // visitor navigates to the app, its document.referrer becomes this 518 // landing page. Forwarding an internal (same-site / sysagent.ai) 519 // referrer would make a direct visit look like a `sysagent.ai` traffic 520 // source, so a direct landing is left unmarked (reported as `direct`). 521 if (document.referrer && isExternalReferrer(document.referrer)) { 522 attribution.set('landing_referrer', document.referrer); 523 } 524 525 if (Array.from(attribution.keys()).length === 0) { 526 return; 527 } 528 529 const isAppLink = (href) => Boolean(href) && ( 530 href.includes('register=true') 531 || /app\.sysagent\.ai\/dashboard/i.test(href) 532 || href.includes('/dashboard/') 533 ); 534 535 document.querySelectorAll('a[href]').forEach((anchor) => { 536 const href = anchor.getAttribute('href'); 537 if (!isAppLink(href)) { 538 return; 539 } 540 try { 541 const url = new URL(href, window.location.href); 542 attribution.forEach((value, key) => { 543 if (!url.searchParams.has(key)) { 544 url.searchParams.set(key, value); 545 } 546 }); 547 anchor.setAttribute('href', url.toString()); 548 } catch (error) { 549 // Skip malformed hrefs without interrupting the others. 550 } 551 }); 552 } catch (error) { 553 // Attribution forwarding is best-effort and must never break the page. 554 } 555}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.