/* ===================================================
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);
}