Files
HOALedgerIQ_Website/app.js
olsch01 4f99e8c71a Replace hero mockup with auto-rotating screenshot carousel
- Swap static dashboard card for 3-slide image carousel in hero section
- Slides auto-advance every 4.5s, pause on hover, support manual prev/next and dot navigation
- Add carousel CSS with fade transition, dot indicators, and glow border treatment
- Add carousel JS with goTo/prev/next/auto-rotate logic
- Images expected at img/screenshot-dashboard.png, img/screenshot-cashflow.png, img/screenshot-capital.png

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 14:42:05 -04:00

181 lines
5.7 KiB
JavaScript

/* HOA LedgerIQ — Frontend form handler + countdown */
// ── Dynamic Countdown ────────────────────────────────────
// Launch date = March 1, 2026 + 60 days = April 30, 2026
// On 3/1/2026 the banner shows "60 days", then counts down daily.
(function initCountdown() {
const launchDate = new Date('2026-04-30T00:00:00');
const now = new Date();
const msPerDay = 1000 * 60 * 60 * 24;
const daysLeft = Math.max(0, Math.ceil((launchDate - now) / msPerDay));
const label = daysLeft === 1 ? 'Day' : 'Days';
const text = daysLeft > 0
? `Launching in ${daysLeft} ${label}`
: 'Now Live!';
const bannerEl = document.getElementById('bannerCountdown');
const signupEl = document.getElementById('signupCountdown');
if (bannerEl) bannerEl.textContent = text;
if (signupEl) signupEl.textContent = text;
})();
// ── Screenshot Carousel ──────────────────────────────────
(function initCarousel() {
const carousel = document.getElementById('screenshotCarousel');
if (!carousel) return;
const slides = carousel.querySelectorAll('.carousel-slide');
const dots = carousel.querySelectorAll('.carousel-dot');
let current = 0;
let timer;
function goTo(index) {
slides[current].classList.remove('active');
dots[current].classList.remove('active');
current = (index + slides.length) % slides.length;
slides[current].classList.add('active');
dots[current].classList.add('active');
}
function next() { goTo(current + 1); }
function prev() { goTo(current - 1); }
function startAuto() {
timer = setInterval(next, 4500);
}
function resetAuto() {
clearInterval(timer);
startAuto();
}
carousel.querySelector('.carousel-next').addEventListener('click', () => { next(); resetAuto(); });
carousel.querySelector('.carousel-prev').addEventListener('click', () => { prev(); resetAuto(); });
dots.forEach(dot => {
dot.addEventListener('click', () => {
goTo(parseInt(dot.dataset.index, 10));
resetAuto();
});
});
// Pause on hover
carousel.addEventListener('mouseenter', () => clearInterval(timer));
carousel.addEventListener('mouseleave', startAuto);
startAuto();
})();
// ── Form Handler ─────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('signupForm');
const submitBtn = document.getElementById('submitBtn');
const btnText = submitBtn?.querySelector('.btn-text');
const btnLoading = submitBtn?.querySelector('.btn-loading');
const successDiv = document.getElementById('signupSuccess');
if (!form) return;
form.addEventListener('submit', async (e) => {
e.preventDefault();
const firstName = form.firstName.value.trim();
const lastName = form.lastName.value.trim();
const email = form.email.value.trim();
const orgName = form.orgName?.value.trim() || '';
const state = form.state?.value || '';
// Basic client-side validation
if (!firstName || !lastName || !email || !orgName || !state) {
highlightEmpty(form);
return;
}
if (!isValidEmail(email)) {
form.email.style.borderColor = '#ef4444';
form.email.focus();
return;
}
// Show loading state
setLoading(true);
const payload = {
firstName,
lastName,
email,
orgName: form.orgName?.value.trim() || '',
state: form.state?.value || '',
role: form.role.value,
unitCount: form.unitCount.value,
betaInterest: form.betaInterest?.checked || false,
source: 'landing_page',
timestamp: new Date().toISOString(),
};
try {
const res = await fetch('/api/leads', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (res.ok) {
showSuccess();
} else {
const data = await res.json().catch(() => ({}));
if (res.status === 409) {
// Already registered
showSuccess('You\'re already on the list! We\'ll be in touch soon.');
} else {
alert(data.error || 'Something went wrong. Please try again.');
setLoading(false);
}
}
} catch (err) {
// Network error — fall back to localStorage so we don't lose the lead
saveLocally(payload);
showSuccess();
}
});
function setLoading(on) {
if (!btnText || !btnLoading) return;
submitBtn.disabled = on;
btnText.classList.toggle('hidden', on);
btnLoading.classList.toggle('hidden', !on);
}
function showSuccess(msg) {
form.classList.add('hidden');
if (msg && successDiv) {
const p = successDiv.querySelector('p');
if (p) p.textContent = msg;
}
successDiv?.classList.remove('hidden');
}
function highlightEmpty(f) {
['firstName', 'lastName', 'email', 'orgName', 'state'].forEach(name => {
const el = f[name];
if (el && !el.value.trim()) {
el.style.borderColor = '#ef4444';
el.addEventListener('input', () => { el.style.borderColor = ''; }, { once: true });
el.addEventListener('change', () => { el.style.borderColor = ''; }, { once: true });
}
});
}
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function saveLocally(payload) {
try {
const existing = JSON.parse(localStorage.getItem('hoa_leads') || '[]');
existing.push(payload);
localStorage.setItem('hoa_leads', JSON.stringify(existing));
} catch (_) { /* best-effort */ }
}
});