/* 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; })(); // ── Benefit Calculator ─────────────────────────────────── (function initCalculator() { const overlay = document.getElementById('calcOverlay'); const openBtn = document.getElementById('openCalc'); const closeBtn = document.getElementById('calcClose'); const submitBtn = document.getElementById('calcSubmit'); const recalcBtn = document.getElementById('calcRecalc'); const calcForm = document.getElementById('calcForm'); const calcRes = document.getElementById('calcResults'); const calcErr = document.getElementById('calcError'); const ctaBtn = document.getElementById('calcCTABtn'); if (!overlay) return; function open() { overlay.classList.add('open'); document.body.style.overflow = 'hidden'; } function close() { overlay.classList.remove('open'); document.body.style.overflow = ''; } openBtn?.addEventListener('click', open); closeBtn?.addEventListener('click', close); overlay.addEventListener('click', e => { if (e.target === overlay) close(); }); document.addEventListener('keydown', e => { if (e.key === 'Escape') close(); }); recalcBtn?.addEventListener('click', () => { calcRes.classList.add('hidden'); calcForm.classList.remove('hidden'); }); // Close modal and scroll to signup when CTA clicked ctaBtn?.addEventListener('click', () => { close(); }); submitBtn?.addEventListener('click', () => { const homesites = parseFloat(document.getElementById('calcHomesites').value) || 0; const propertyType = document.getElementById('calcPropertyType').value; const annualIncome = parseFloat(document.getElementById('calcAnnualIncome').value) || 0; const paymentFreq = document.getElementById('calcPaymentFreq').value; const reserveFunds = parseFloat(document.getElementById('calcReserveFunds').value) || 0; const interest2025 = parseFloat(document.getElementById('calcInterest2025').value) || 0; if (!homesites || !annualIncome) { calcErr.classList.remove('hidden'); return; } calcErr.classList.add('hidden'); // ── Conservative investment assumptions ── // Operating cash: depending on payment frequency, portion investable in high-yield savings const opMultiplier = { monthly: 0.10, quarterly: 0.20, annually: 0.35 }[paymentFreq] || 0.10; const opRate = 0.040; // 4.0% money market / HYSA const resRatio = 0.65; // 65% of reserves investable (keep 35% liquid) const resRate = 0.0425; // 4.25% CD ladder / short-term treasuries const investableOp = annualIncome * opMultiplier; const investableRes = reserveFunds * resRatio; const opInterest = Math.round(investableOp * opRate); const resInterest = Math.round(investableRes * resRate); const totalPotential = opInterest + resInterest; const increase = totalPotential - interest2025; const pctIncrease = interest2025 > 0 ? Math.round((increase / interest2025) * 100) : (totalPotential > 0 ? 100 : 0); // ── Populate results ── const fmt = n => '$' + Math.round(n).toLocaleString(); document.getElementById('resultAmount').textContent = fmt(totalPotential); document.getElementById('resultCurrent').textContent = fmt(interest2025); document.getElementById('resultOperating').textContent = fmt(opInterest); document.getElementById('resultReserve').textContent = fmt(resInterest); const badge = document.getElementById('resultBadge'); if (increase > 0) { badge.textContent = `+${fmt(increase)} · +${pctIncrease}%`; badge.style.display = 'inline-block'; } else { badge.style.display = 'none'; } // ── AI-style suggestion text ── const typeLabels = { sfh:'single-family home', townhomes:'townhome', condos:'condo', mixed:'mixed-use', '':'' }; const typeLabel = typeLabels[propertyType] || ''; const freqLabel = { monthly:'monthly', quarterly:'quarterly', annually:'annual' }[paymentFreq]; const communityDesc = [homesites && `${homesites}-unit`, typeLabel, 'community'].filter(Boolean).join(' '); let ai = `Based on your ${communityDesc} collecting ${fmt(annualIncome)} in ${freqLabel} dues`; if (reserveFunds > 0) ai += ` and ${fmt(reserveFunds)} in reserve funds`; ai += `, a conservative investment strategy could generate approximately ${fmt(totalPotential)} in annual interest income. `; if (resInterest > 0) ai += `Deploying ${fmt(investableRes)} of your reserve funds into a short-term CD ladder at ~4.25% yields ${fmt(resInterest)} annually. `; if (opInterest > 0) ai += `Keeping a ${fmt(investableOp)} operating cash buffer in a high-yield money market at ~4.0% adds another ${fmt(opInterest)}. `; if (interest2025 > 0 && increase > 0) { ai += `That's a ${fmt(increase)} improvement (+${pctIncrease}%) over your 2025 interest income of ${fmt(interest2025)} — with no additional risk.`; } else if (interest2025 === 0) { ai += `This would represent entirely new interest income for your community at no additional risk.`; } document.getElementById('calcAiText').textContent = ai; // ── Animate the main number ── animateValue(document.getElementById('resultAmount'), 0, totalPotential); calcForm.classList.add('hidden'); calcRes.classList.remove('hidden'); }); function animateValue(el, from, to) { const duration = 900; const start = performance.now(); function step(now) { const progress = Math.min((now - start) / duration, 1); const ease = 1 - Math.pow(1 - progress, 3); el.textContent = '$' + Math.round(from + (to - from) * ease).toLocaleString(); if (progress < 1) requestAnimationFrame(step); } requestAnimationFrame(step); } })(); // ── 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 */ } } });