Files
help4bis-com/site/assets/js/main.js
T

231 lines
9.6 KiB
JavaScript

'use strict';
/* ══════════════════════════════════════
NAVIGATION — DROPDOWN (click-based)
══════════════════════════════════════ */
function initDropdowns() {
document.querySelectorAll('.nav__dropdown').forEach(item => {
const trigger = item.querySelector('.nav__trigger');
const menu = item.querySelector('.nav__dropdown-menu');
if (!trigger || !menu) return;
trigger.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = menu.classList.toggle('open');
trigger.setAttribute('aria-expanded', isOpen);
// Close siblings
document.querySelectorAll('.nav__dropdown-menu.open').forEach(m => {
if (m !== menu) {
m.classList.remove('open');
m.closest('.nav__dropdown').querySelector('.nav__trigger')?.setAttribute('aria-expanded','false');
}
});
});
});
// Close on outside click
document.addEventListener('click', () => {
document.querySelectorAll('.nav__dropdown-menu.open').forEach(m => {
m.classList.remove('open');
m.closest('.nav__dropdown').querySelector('.nav__trigger')?.setAttribute('aria-expanded','false');
});
});
// Close on Escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
document.querySelectorAll('.nav__dropdown-menu.open').forEach(m => {
m.classList.remove('open');
m.closest('.nav__dropdown').querySelector('.nav__trigger')?.setAttribute('aria-expanded','false');
});
}
});
}
/* ══════════════════════════════════════
NAVIGATION — MOBILE BURGER
══════════════════════════════════════ */
function initMobileNav() {
const burger = document.getElementById('nav-burger');
const navLinks = document.getElementById('nav-links');
if (!burger || !navLinks) return;
burger.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = navLinks.classList.toggle('nav--open');
burger.setAttribute('aria-expanded', isOpen);
document.body.style.overflow = isOpen ? 'hidden' : '';
burger.innerHTML = isOpen ? iconX() : iconMenu();
});
// Close on link click
navLinks.querySelectorAll('a').forEach(link => {
link.addEventListener('click', () => {
navLinks.classList.remove('nav--open');
burger.setAttribute('aria-expanded','false');
document.body.style.overflow = '';
burger.innerHTML = iconMenu();
});
});
// Close on outside click
document.addEventListener('click', (e) => {
if (!burger.contains(e.target) && !navLinks.contains(e.target)) {
navLinks.classList.remove('nav--open');
burger.setAttribute('aria-expanded','false');
document.body.style.overflow = '';
burger.innerHTML = iconMenu();
}
});
}
function iconMenu() {
return `<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>`;
}
function iconX() {
return `<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
}
/* ══════════════════════════════════════
ACTIVE NAV LINK
══════════════════════════════════════ */
function initActiveNav() {
const path = window.location.pathname;
const file = path.split('/').pop() || 'index.html';
document.querySelectorAll('.nav__links a').forEach(a => {
const href = a.getAttribute('href');
if (href && (href === file || (file === '' && href === 'index.html'))) {
a.classList.add('active');
}
});
}
/* ══════════════════════════════════════
STICKY NAV SHADOW
══════════════════════════════════════ */
function initStickyNav() {
const nav = document.querySelector('.nav');
if (!nav) return;
const update = () => nav.classList.toggle('scrolled', window.scrollY > 30);
window.addEventListener('scroll', update, { passive: true });
update();
}
/* ══════════════════════════════════════
SCROLL REVEAL
══════════════════════════════════════ */
function initReveal() {
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('visible');
io.unobserve(e.target);
}
});
}, { threshold: 0.06, rootMargin: '0px 0px -24px 0px' });
// Individual reveal elements
document.querySelectorAll('.reveal').forEach(el => io.observe(el));
// Stagger groups (observe the container, trigger class on it)
document.querySelectorAll('.stagger').forEach(el => io.observe(el));
}
/* ══════════════════════════════════════
SMOOTH ANCHOR SCROLL
══════════════════════════════════════ */
function initAnchorScroll() {
document.querySelectorAll('a[href^="#"]').forEach(a => {
a.addEventListener('click', (e) => {
const id = a.getAttribute('href');
const target = document.querySelector(id);
if (target) {
e.preventDefault();
const top = target.getBoundingClientRect().top + window.scrollY - 76;
window.scrollTo({ top, behavior: 'smooth' });
}
});
});
}
/* ══════════════════════════════════════
FORM HANDLER
══════════════════════════════════════ */
const formShownAt = Date.now();
function initForms() {
document.querySelectorAll('.intake-form').forEach(form => {
form.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = form.querySelector('[type="submit"]');
const originalText = btn.textContent;
btn.textContent = 'Sending…';
btn.disabled = true;
// Collect all named fields
const data = {};
new FormData(form).forEach((val, key) => { data[key] = val; });
// Spam check: how long the visitor took to fill the form (measured on their own clock)
data.fill_ms = Date.now() - formShownAt;
try {
const res = await fetch('/contact.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const json = await res.json();
if (json.ok) {
form.innerHTML = `
<div style="text-align:center;padding:2.5rem 1rem;">
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 24 24" fill="none" stroke="#16A34A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin:0 auto 1.25rem;display:block"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
<h3 style="font-family:'DM Sans',sans-serif;color:#1A1A2E;margin-bottom:.5rem;font-size:22px;">Sent.</h3>
<p style="color:#6B7280;font-size:15px;max-width:400px;margin:0 auto;">We'll be back within one business day with a clear recommendation and price.</p>
</div>`;
} else {
throw new Error(json.error || 'Send failed');
}
} catch (err) {
// Show inline error — don't wipe the form
let errEl = form.querySelector('.form-error');
if (!errEl) {
errEl = document.createElement('p');
errEl.className = 'form-error';
errEl.style.cssText = 'color:#E8352A;font-size:14px;margin-top:0.75rem;';
form.querySelector('.intake-form__actions').appendChild(errEl);
}
errEl.textContent = err.message || 'Something went wrong. Please email project@help4bis.com directly.';
btn.textContent = originalText;
btn.disabled = false;
}
});
});
}
/* ══════════════════════════════════════
BACK TO TOP
══════════════════════════════════════ */
function initBackToTop() {
const btn = document.getElementById('back-to-top');
if (!btn) return;
window.addEventListener('scroll', () => {
btn.classList.toggle('visible', window.scrollY > 500);
}, { passive: true });
btn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
}
/* ══════════════════════════════════════
INIT
══════════════════════════════════════ */
document.addEventListener('DOMContentLoaded', () => {
initDropdowns();
initMobileNav();
initActiveNav();
initStickyNav();
initReveal();
initAnchorScroll();
initForms();
initBackToTop();
});