// Landing page — dual-split hero, two audiences, pricing, CTA const { useEffect: useLE, useRef: useRef, useState: useState, useCallback: useCallback } = React; /* ─── POC contact modal ─── */ function ContactModal({ open, onClose }) { const ROLES = ['Valuer','Broker / Agent','Developer','REIT','Commercial Bank','Mortgage Originator','Regulator','Other']; const VOLUMES = ['< 10 / month','10 – 50 / month','50 – 200 / month','200 – 1,000 / month','1,000+ / month']; const empty = { name:'', company:'', role:'', email:'', phone:'', volume:'', message:'' }; const [form, setForm] = useState(empty); const [sent, setSent] = useState(false); const [errors, setErrors] = useState({}); useLE(() => { if (open) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; setSent(false); setForm(empty); setErrors({}); } return () => { document.body.style.overflow = ''; }; }, [open]); useLE(() => { const handler = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, [onClose]); const set = (k) => (e) => setForm(f => ({ ...f, [k]: e.target.value })); const validate = () => { const e = {}; if (!form.name.trim()) e.name = 'Required'; if (!form.company.trim()) e.company = 'Required'; if (!form.role) e.role = 'Required'; if (!form.email.trim() || !/\S+@\S+\.\S+/.test(form.email)) e.email = 'Valid email required'; return e; }; const [submitting, setSubmitting] = useState(false); const [submitError, setSubmitError] = useState(''); const submit = async (ev) => { ev.preventDefault(); setSubmitError(''); const e = validate(); if (Object.keys(e).length) { setErrors(e); return; } setSubmitting(true); try { const payload = { name: form.name, company: form.company, role: form.role === 'Other' ? (form.roleOther || 'Other') : form.role, email: form.email, phone: form.phone, volume: form.volume, message: form.message, }; const res = await fetch('https://formspree.io/f/xgorzrpy', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(payload), }); if (res.ok) { setSent(true); } else { setSubmitError("We couldn't submit your request just now. Please try again in a moment."); } } catch (_) { setSubmitError("Network error — please check your connection and try again."); } finally { setSubmitting(false); } }; if (!open) return null; return (
e.stopPropagation()}> {/* ── Left dark panel ── */}
Early access

Live valuation. Today's call.

  • Quick reply — we'll get back to you as soon as possible.
  • 20 minutes — no prep, no slides.
  • Your property — real Lagos asset, priced live.
{/* ── Right form panel ── */}
{sent ? (

You're on the list.

We'll reach out to {form.email} as soon as possible to book your POC session.

) : ( <>
Request POC access
Takes 60 seconds. No commitment.
{/* Role chips */}
{ROLES.map(r => ( ))} {form.role === 'Other' && ( setForm(f => ({...f, roleOther: e.target.value}))} autoFocus /> )}
{errors.role &&
{errors.role}
}
{/* Name + Company inline */}
{errors.name &&
{errors.name}
}
{errors.company &&
{errors.company}
}
{/* Email + Phone inline */}
{errors.email &&
{errors.email}
}
{/* Volume */}
{VOLUMES.map(v => ( ))}
{/* Message */}