/* =================================================== AptaSentry — Main JavaScript =================================================== */ const API_URL = 'https://xytbunkpp2.execute-api.us-east-1.amazonaws.com'; document.addEventListener('DOMContentLoaded', () => { /* ----- Theme Toggle ----- */ initThemeToggle(); /* ----- Innovate Feature Tabs ----- */ initInnovateTabs(); /* ----- Card 3D Tilt ----- */ initCardTilt(); /* ----- Product Media Slots ----- */ initProductMediaSlots(); initHeroImageSlot(); /* ----- Forms ----- */ initContactForms(); initSubscribeForms(); /* ----- Sticky Navbar ----- */ const navbar = document.getElementById('navbar'); if (navbar) { const tick = () => { if (window.scrollY > 30) navbar.classList.add('scrolled'); else navbar.classList.remove('scrolled'); }; window.addEventListener('scroll', tick, { passive: true }); tick(); } /* ----- Mobile Menu ----- */ const hamburger = document.getElementById('hamburger'); const mobileMenu = document.getElementById('mobileMenu'); if (hamburger && mobileMenu) { hamburger.setAttribute('aria-expanded', 'false'); hamburger.setAttribute('aria-controls', mobileMenu.id || 'mobileMenu'); hamburger.addEventListener('click', () => { const open = mobileMenu.classList.toggle('open'); hamburger.setAttribute('aria-expanded', String(open)); }); document.addEventListener('click', (e) => { if (!navbar.contains(e.target) && !mobileMenu.contains(e.target)) { mobileMenu.classList.remove('open'); hamburger.setAttribute('aria-expanded', 'false'); } }); mobileMenu.addEventListener('click', (e) => { if (e.target.closest('a')) { mobileMenu.classList.remove('open'); hamburger.setAttribute('aria-expanded', 'false'); } }); } /* ----- Active Nav Link ----- */ const path = window.location.pathname.replace(/\\/g, '/'); document.querySelectorAll('.nav-links a, .mobile-menu a').forEach(a => { const href = a.getAttribute('href') || ''; const norm = href.replace(/\\/g, '/').replace(/^\.\//, ''); const file = path.split('/').pop(); if (file && norm.endsWith(file)) a.classList.add('active'); else if ((file === '' || file === 'index.html') && (norm === '' || norm === 'index.html')) a.classList.add('active'); }); /* ----- Intersection Observer (fade-in + counters) ----- */ const fadeEls = document.querySelectorAll('.fade-in'); if (fadeEls.length) { const obs = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('visible'); obs.unobserve(e.target); } }); }, { threshold: 0.08 }); fadeEls.forEach(el => obs.observe(el)); } /* ----- Animated Counters ----- */ const counters = document.querySelectorAll('[data-count]'); if (counters.length) { const cObs = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) { runCounter(e.target); cObs.unobserve(e.target); } }); }, { threshold: 0.5 }); counters.forEach(c => cObs.observe(c)); } /* ----- Hero Canvas Particle Network ----- */ const canvas = document.getElementById('hero-canvas'); if (canvas) initParticles(canvas); /* ----- Pricing Toggle ----- */ const toggleBtns = document.querySelectorAll('.price-toggle-btn'); toggleBtns.forEach(btn => { btn.addEventListener('click', () => { toggleBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); const period = btn.dataset.period; document.querySelectorAll('[data-price-monthly]').forEach(el => { const value = period === 'yearly' ? el.dataset.priceYearly : el.dataset.priceMonthly; const periodText = period === 'yearly' ? '/year' : '/mo'; el.innerHTML = `${value} ${periodText}`; }); }); }); }); /* ----- Persistent Light/Dark Theme ----- */ function initThemeToggle() { const root = document.documentElement; const stored = localStorage.getItem('aptasentry-theme'); const prefersLight = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches; const initial = stored || (prefersLight ? 'light' : 'dark'); setTheme(initial); const navRight = document.querySelector('.nav-right'); const mobileMenu = document.getElementById('mobileMenu'); const desktopToggle = buildThemeToggle(false); const mobileToggle = buildThemeToggle(true); if (navRight) navRight.insertBefore(desktopToggle, navRight.firstChild); if (mobileMenu) mobileMenu.insertBefore(mobileToggle, mobileMenu.firstChild); updateToggles(); function buildThemeToggle(showText) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'theme-toggle'; btn.setAttribute('aria-label', 'Switch color theme'); btn.addEventListener('click', () => { const next = root.dataset.theme === 'light' ? 'dark' : 'light'; setTheme(next); localStorage.setItem('aptasentry-theme', next); updateToggles(); }); btn.dataset.showText = String(showText); return btn; } function setTheme(theme) { const isLight = theme === 'light'; root.dataset.theme = isLight ? 'light' : 'dark'; document.querySelectorAll('img.logo-image, img.footer-logo-image').forEach(img => { img.src = img.src.replace( /aptasentry-logo-(?:dark|transparent|hd)?\.png$|aptasentry-logo\.png$/, isLight ? 'aptasentry-logo-transparent.png' : 'aptasentry-logo-dark.png' ); }); } function updateToggles() { const isLight = root.dataset.theme === 'light'; document.querySelectorAll('.theme-toggle').forEach(btn => { const label = isLight ? 'Switch to dark mode' : 'Switch to light mode'; const text = btn.dataset.showText === 'true' ? `${isLight ? 'Dark mode' : 'Light mode'}` : ''; btn.setAttribute('aria-label', label); btn.setAttribute('title', label); btn.innerHTML = `${isLight ? moonIcon() : sunIcon()}${text}`; }); } function sunIcon() { return ''; } function moonIcon() { return ''; } } /* ----- Home Hero Image Slot ----- */ function initHeroImageSlot() { const visual = document.querySelector('.home-product-visual'); const img = visual ? visual.querySelector('img') : null; if (!visual || !img) return; if (img.complete && img.naturalWidth > 0) { visual.classList.add('has-image'); return; } img.addEventListener('load', () => visual.classList.add('has-image')); img.addEventListener('error', () => img.remove()); } /* ----- Product Image Placeholders ----- */ function initProductMediaSlots() { const imageMap = { aptared: 'assets/products/aptared.webp', agentred: 'assets/products/agentred.webp', aptaresolve: 'assets/products/aptaresolve.webp', aptasignal: 'assets/products/aptasignal.webp', aptabadging: 'assets/products/aptabadging.webp', aptaconsult: 'assets/products/aptaconsult.webp' }; const prefix = window.location.pathname.replace(/\\/g, '/').includes('/blog/') ? '../' : ''; document.querySelectorAll('.product-card').forEach(card => { if (card.querySelector('.product-media')) return; const title = card.querySelector('h3'); if (!title) return; const key = title.textContent.trim().toLowerCase(); const relativeSrc = imageMap[key]; if (!relativeSrc) return; const media = document.createElement('div'); media.className = 'product-media'; media.dataset.label = `${title.textContent.trim()} image slot`; const img = document.createElement('img'); img.src = prefix + relativeSrc; img.alt = `${title.textContent.trim()} product interface`; img.loading = 'lazy'; img.decoding = 'async'; img.addEventListener('load', () => media.classList.add('has-image')); img.addEventListener('error', () => img.remove()); media.appendChild(img); const icon = card.querySelector('.p-icon'); if (icon) icon.insertAdjacentElement('afterend', media); else card.prepend(media); }); } /* ----- Counter Animation ----- */ function runCounter(el) { const raw = el.dataset.count; const match = raw.match(/([\d.]+)/); if (!match) { el.textContent = raw; return; } const target = parseFloat(match[1]); const prefix = raw.slice(0, raw.indexOf(match[1])); const suffix = raw.slice(raw.indexOf(match[1]) + match[1].length); const isFloat = match[1].includes('.'); const dur = 1600; const steps = 60; const inc = target / steps; let cur = 0; const timer = setInterval(() => { cur += inc; if (cur >= target) { cur = target; clearInterval(timer); } el.textContent = prefix + (isFloat ? cur.toFixed(1) : Math.floor(cur)) + suffix; }, dur / steps); } /* ----- Particle Network ----- */ function initParticles(canvas) { const ctx = canvas.getContext('2d'); let W, H, pts; const MAX_DIST = 130; const COUNT = () => Math.max(30, Math.floor((W * H) / 14000)); function resize() { W = canvas.width = canvas.offsetWidth; H = canvas.height = canvas.offsetHeight; build(); } function build() { const n = COUNT(); pts = Array.from({ length: n }, () => ({ x: Math.random() * W, y: Math.random() * H, vx: (Math.random() - 0.5) * 0.28, vy: (Math.random() - 0.5) * 0.28, r: Math.random() * 1.4 + 0.4, a: Math.random() * 0.45 + 0.1 })); } function draw() { ctx.clearRect(0, 0, W, H); for (const p of pts) { p.x = (p.x + p.vx + W) % W; p.y = (p.y + p.vy + H) % H; ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fillStyle = `rgba(96,165,250,${p.a})`; ctx.fill(); } for (let i = 0; i < pts.length; i++) { for (let j = i + 1; j < pts.length; j++) { const dx = pts[i].x - pts[j].x, dy = pts[i].y - pts[j].y; const d = Math.sqrt(dx * dx + dy * dy); if (d < MAX_DIST) { ctx.beginPath(); ctx.moveTo(pts[i].x, pts[i].y); ctx.lineTo(pts[j].x, pts[j].y); ctx.strokeStyle = `rgba(59,130,246,${0.18 * (1 - d / MAX_DIST)})`; ctx.lineWidth = 0.6; ctx.stroke(); } } } requestAnimationFrame(draw); } window.addEventListener('resize', resize, { passive: true }); resize(); draw(); } /* ----- 3D Card Tilt ----- */ function initCardTilt() { const TILT_MAX = 10; /* max rotation degrees */ const LIFT_Z = 20; /* translateZ on hover */ const SCALE_IN = 1.03; /* scale on hover */ const SCALE_CLICK = 0.97;/* scale on click */ const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (prefersReduced) return; document.querySelectorAll('.ap-card').forEach(card => { /* Inject shine overlay */ const shine = document.createElement('div'); shine.className = 'ap-card-shine'; card.appendChild(shine); let raf; function applyTilt(e) { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => { const r = card.getBoundingClientRect(); const x = e.clientX - r.left; const y = e.clientY - r.top; const cx = r.width / 2; const cy = r.height / 2; const rX = ((y - cy) / cy) * -TILT_MAX; const rY = ((x - cx) / cx) * TILT_MAX; card.style.transform = `perspective(900px) rotateX(${rX}deg) rotateY(${rY}deg) translateZ(${LIFT_Z}px) scale(${SCALE_IN})`; card.style.boxShadow = `${-rY * 1.6}px ${rX * 1.6}px 48px rgba(37,99,235,0.22), 0 24px 64px rgba(0,0,0,0.32)`; /* Move shine toward cursor */ shine.style.setProperty('--sx', `${(x / r.width) * 100}%`); shine.style.setProperty('--sy', `${(y / r.height) * 100}%`); }); } function resetTilt() { cancelAnimationFrame(raf); card.style.transition = 'transform 0.65s cubic-bezier(0.23,1,0.32,1), box-shadow 0.65s ease, border-color 0.22s ease'; card.style.transform = ''; card.style.boxShadow = ''; shine.style.opacity = '0'; } card.addEventListener('mouseenter', () => { card.style.transition = 'box-shadow 0.15s ease, border-color 0.22s ease'; shine.style.opacity = '1'; card.style.borderColor = 'var(--c-border-h)'; }); card.addEventListener('mousemove', applyTilt); card.addEventListener('mouseleave', resetTilt); /* Click: punch-in then spring back */ card.addEventListener('mousedown', () => { card.style.transition = 'transform 0.1s ease'; card.style.transform = `perspective(900px) translateZ(4px) scale(${SCALE_CLICK})`; }); card.addEventListener('mouseup', (e) => { card.style.transition = 'transform 0.18s cubic-bezier(0.34,1.56,0.64,1)'; applyTilt(e); }); }); } /* ----- Contact Form Submission ----- */ function initContactForms() { document.querySelectorAll('.home-contact-form').forEach(form => { form.addEventListener('submit', async (e) => { e.preventDefault(); const btn = form.querySelector('button[type="submit"]'); const original = btn.textContent; btn.textContent = 'Sending…'; btn.disabled = true; const data = { name: form.querySelector('[name="name"]').value.trim(), email: form.querySelector('[name="email"]').value.trim(), company: form.querySelector('[name="company"]').value.trim(), role: form.querySelector('[name="role"]')?.value.trim() || '', interest: form.querySelector('[name="interest"]').value.trim(), message: form.querySelector('[name="message"]').value.trim() }; try { const res = await fetch(`${API_URL}/contact`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!res.ok) throw new Error(); btn.textContent = 'Message Sent!'; form.reset(); setTimeout(() => { btn.textContent = original; btn.disabled = false; }, 4000); } catch { btn.textContent = 'Failed — please try again'; btn.disabled = false; setTimeout(() => { btn.textContent = original; }, 4000); } }); }); } /* ----- Newsletter & Free Trial Form Submission ----- */ function initSubscribeForms() { const submitEmail = async (email, source) => { const res = await fetch(`${API_URL}/subscribe`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, source }) }); if (!res.ok) throw new Error(); }; document.querySelectorAll('.home-subscribe-form').forEach(form => { form.addEventListener('submit', async (e) => { e.preventDefault(); const input = form.querySelector('input[type="email"]'); const btn = form.querySelector('button[type="submit"]'); if (!input.value) return; btn.disabled = true; try { await submitEmail(input.value.trim(), 'newsletter'); input.value = ''; input.placeholder = 'Subscribed!'; setTimeout(() => { input.placeholder = 'Your Email'; btn.disabled = false; }, 4000); } catch { input.placeholder = 'Failed — try again'; setTimeout(() => { input.placeholder = 'Your Email'; btn.disabled = false; }, 4000); } }); }); document.querySelectorAll('.freetrial-form').forEach(form => { form.addEventListener('submit', async (e) => { e.preventDefault(); const input = form.querySelector('input[type="email"]'); const btn = form.querySelector('button[type="submit"]'); if (!input.value) return; btn.disabled = true; try { await submitEmail(input.value.trim(), 'free-trial'); input.value = ''; input.placeholder = 'Thank you!'; setTimeout(() => { input.placeholder = 'Your Email'; btn.disabled = false; }, 4000); } catch { input.placeholder = 'Failed — try again'; setTimeout(() => { input.placeholder = 'Your Email'; btn.disabled = false; }, 4000); } }); }); } /* ----- Innovate Feature Tabs (auto-rotate) ----- */ function initInnovateTabs() { const items = document.querySelectorAll('.innovate-item'); const panels = document.querySelectorAll('.innovate-panel'); const section = document.querySelector('.innovate-section'); if (!items.length) return; const INTERVAL = 2500; /* ms per slide */ let current = 0; let timer = null; let paused = false; function goTo(idx) { items.forEach(i => i.classList.remove('is-active')); panels.forEach(p => p.classList.remove('is-active')); items[idx].classList.add('is-active'); document.querySelector(`.innovate-panel[data-panel="${idx}"]`)?.classList.add('is-active'); /* restart progress bar */ items.forEach(i => { const bar = i.querySelector('.innovate-item-bar'); if (bar) { bar.style.transition = 'none'; bar.style.height = '0%'; } }); requestAnimationFrame(() => { requestAnimationFrame(() => { const bar = items[idx].querySelector('.innovate-item-bar'); if (bar) { bar.style.transition = `height ${INTERVAL}ms linear`; bar.style.height = '60%'; } }); }); current = idx; } function next() { goTo((current + 1) % items.length); } function startTimer() { clearInterval(timer); timer = setInterval(() => { if (!paused) next(); }, INTERVAL); } /* Manual click resets timer */ items.forEach((item, idx) => { item.addEventListener('click', () => { goTo(idx); startTimer(); }); }); /* Pause on hover over the whole section */ if (section) { section.addEventListener('mouseenter', () => { paused = true; }); section.addEventListener('mouseleave', () => { paused = false; }); } /* Only auto-rotate when section is visible */ const obs = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { startTimer(); } else { clearInterval(timer); } }); }, { threshold: 0.3 }); if (section) obs.observe(section); goTo(0); }