/* ============================================================
New Liberty Homes — Shared components
============================================================ */
const { useState } = React;
const Icon = window.Icon;
// ── Lead delivery ───────────────────────────────────────────
// All website form submissions are emailed to this address.
const LEAD_EMAIL = 'info@newlibertyhomes.com';
// ...and also POSTed to the CRM via this Zapier Catch Hook.
const CRM_WEBHOOK = 'https://hooks.zapier.com/hooks/catch/985690/434nvhk/';
// Uses FormSubmit (https://formsubmit.co) — no backend required. The FIRST
// submission triggers a one-time confirmation email to LEAD_EMAIL; click the
// link in it once to activate delivery. After that every submission is emailed.
function sendLead(formName, fields) {
const flat = { 'Form': formName, 'Submitted': new Date().toLocaleString() };
for (const [k, v] of Object.entries(fields || {})) {
const label = k.replace(/([A-Z])/g, ' $1').replace(/^./, c => c.toUpperCase()).trim();
flat[label] = Array.isArray(v) ? (v.length ? v.join(', ') : '—') : (v === '' || v == null ? '—' : v);
}
// 1) Email via FormSubmit
const payload = { _subject: `New Liberty Homes — ${formName}`, _template: 'table', _captcha: 'false', ...flat };
try {
fetch('https://formsubmit.co/ajax/' + LEAD_EMAIL, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify(payload),
}).catch(() => {});
} catch (e) { /* fire-and-forget */ }
// 2) Send to CRM via Zapier webhook (all fields, clean keys + metadata)
try {
const crmPayload = {
form: formName,
source: 'newlibertyhomes.com',
submittedAt: new Date().toISOString(),
pageUrl: (typeof window !== 'undefined' && window.location) ? window.location.href : '',
...(fields || {}),
};
// Normalize arrays (e.g. checkbox groups) to comma-joined strings for the CRM.
for (const [k, v] of Object.entries(crmPayload)) {
if (Array.isArray(v)) crmPayload[k] = v.join(', ');
}
fetch(CRM_WEBHOOK, {
method: 'POST',
// text/plain avoids a CORS preflight; Zapier still parses the JSON body.
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
body: JSON.stringify(crmPayload),
}).catch(() => {});
} catch (e) { /* fire-and-forget */ }
}
// ── Logo Mark ────────────────────────────────────────────────
function BrandMark({ size = 52 }) {
return (
{/* Stylized house with star */}
);
}
// ── Status Badge ─────────────────────────────────────────────
function StatusBadge({ status }) {
const cls = status.toLowerCase();
return {status} ;
}
// ── 3D Tour Modal (embedded iframe — stays on same tab) ──────
function Tour3DModal({ url, title, onClose }) {
React.useEffect(() => {
const onKey = (e) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', onKey);
const prev = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
window.removeEventListener('keydown', onKey);
document.body.style.overflow = prev;
};
}, [onClose]);
return (
e.stopPropagation()}>
▶ 3D Tour{title ? ` — ${title}` : ''}
✕
);
}
// ── Video Walkthrough Modal ──────────────────────────────────
function VideoTourModal({ url, title, onClose }) {
React.useEffect(() => {
const onKey = (e) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', onKey);
const prev = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
window.removeEventListener('keydown', onKey);
document.body.style.overflow = prev;
};
}, [onClose]);
return (
e.stopPropagation()}>
▶ Video Walkthrough{title ? ` — ${title}` : ''}
✕
);
}
// ── Home Card ────────────────────────────────────────────────
function HomeCard({ home, onView, onNotify }) {
const isComingSoon = home.status === 'Coming Soon';
const [tourOpen, setTourOpen] = useState(false);
const open = () => onView && onView(home);
return (
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(); } }}
>
e.stopPropagation()}>
{Array.isArray(home.gallery) && home.gallery[0] ? (
) : (
{home.photosComingSoon ? 'Photos coming soon' : 'Home photo'}
)}
{home.tour3d && (
{ e.stopPropagation(); setTourOpen(true); }}
>
▶ 3D Tour
)}
{tourOpen && (
setTourOpen(false)} />
)}
{home.location}
{home.name}
{home.beds && (
{home.beds}
Beds
)}
{home.baths && (
{home.baths}
Baths
)}
{home.sqft && (
{home.sqft}
Sq Ft
)}
{home.lotAcres && (
{home.lotAcres}
Lot Acres
)}
{!home.beds && !home.baths && !home.sqft && !home.lotAcres && (
Details coming soon
)}
{home.price ? ${home.price} : home.priceLabel ? {home.priceLabel} : null}
{ e.stopPropagation(); onView && onView(home); }}>
View
{ e.stopPropagation(); onNotify && onNotify(home); }}>
{home.ctaLabel || 'Inquire'}
);
}
// ── Get Notified Modal ──────────────────────────────────────
function GetNotifiedModal({ home, onClose }) {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [submitted, setSubmitted] = useState(false);
if (!home) return null;
return (
e.stopPropagation()}>
✕
{submitted ? (
🔔
You're on the alert list!
Thanks, {name.split(' ')[0] || 'there'} — we'll let you know the moment {home.name} in {home.location} is available. Usually 7–14 days before it's public.
) : (
<>
Coming Soon · {home.location}
{home.name}
This home isn't here yet, but it's on the way. Drop your info and we'll text and email you the second it's ready to tour.
>
)}
);
}
// ── Home Details Modal ──────────────────────────────────────
function HomeDetailsModal({ home, onClose }) {
const [tab, setTab] = useState('Photos');
const [preApprovalOpen, setPreApprovalOpen] = useState(false);
const [tour3dOpen, setTour3dOpen] = useState(false);
const [videoOpen, setVideoOpen] = useState(false);
const [lightboxIdx, setLightboxIdx] = useState(null);
const galleryLen = Array.isArray(home.gallery) ? home.gallery.length : 0;
const openLightbox = (i) => setLightboxIdx(i);
const closeLightbox = () => setLightboxIdx(null);
const lbNext = () => setLightboxIdx(i => (i + 1) % galleryLen);
const lbPrev = () => setLightboxIdx(i => (i - 1 + galleryLen) % galleryLen);
// Mobile swipe support for the lightbox
const touchRef = React.useRef(null);
const onLbTouchStart = (e) => {
const t = e.touches[0];
touchRef.current = { x: t.clientX, y: t.clientY };
};
const onLbTouchEnd = (e) => {
if (!touchRef.current) return;
const t = e.changedTouches[0];
const dx = t.clientX - touchRef.current.x;
const dy = t.clientY - touchRef.current.y;
touchRef.current = null;
// Treat as a swipe only when mostly horizontal and past a threshold
if (Math.abs(dx) > 50 && Math.abs(dx) > Math.abs(dy) * 1.5) {
if (dx < 0) lbNext();
else lbPrev();
}
};
React.useEffect(() => {
if (lightboxIdx === null) return;
const onKey = (e) => {
if (e.key === 'Escape') closeLightbox();
else if (e.key === 'ArrowRight') lbNext();
else if (e.key === 'ArrowLeft') lbPrev();
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [lightboxIdx, galleryLen]);
if (!home) return null;
const tabs = ['Photos', 'Floor Plans', 'Specs'].filter(t => !(t === 'Floor Plans' && home.hideFloorPlan));
const defaultFeatures = ['Garage Ready', 'Walk-in Pantry', 'Endwall Entry', 'Kitchen Island', 'Freestanding Bath Tub', 'Utility Room', 'Walk-in Shower', 'Energy Star Appliances'];
const features = home.features || defaultFeatures;
const description = home.description || `This manufactured home is built to the federal (HUD) building code for manufactured housing. Quality construction, modern finishes, and an open floor plan designed for real Texas families.`;
const hasSitePackage = Array.isArray(home.sitePackage) && home.sitePackage.length > 0;
const comingSoon = !!home.detailsComingSoon;
// ── Spec rows (auto-populated from data) ──
const specRows = [
home.address && { label: 'Address', value: home.address },
home.brand && { label: 'Brand', value: home.brand },
home.model && { label: 'Model', value: home.model },
home.modelYear && { label: 'Model Year', value: home.modelYear },
home.sectionType && { label: 'Section', value: home.sectionType },
home.beds && { label: 'Bedrooms', value: home.beds },
home.baths && { label: 'Bathrooms', value: home.baths },
home.sqft && { label: 'Square Feet', value: home.sqft },
home.lotAcres && { label: 'Lot Acres', value: home.lotAcres },
home.constructionType && { label: 'Construction', value: `${home.constructionType} — Federal Manufactured Housing Code` },
].filter(Boolean);
return (
e.stopPropagation()}>
✕
{/* Gallery */}
{Array.isArray(home.gallery) && home.gallery[0] ? (
openLightbox(0)}
style={{ cursor: 'zoom-in' }}
/>
) : (
Hero shot · {home.name}
)}
{home.videoTour ? (
setVideoOpen(true)}
>
▶ Video Walkthrough
) : home.tour3d ? (
setTour3dOpen(true)}
>
▶ Explore 3D Space
) : (
▶ Explore 3D Space
)}
{Array.isArray(home.gallery) && home.gallery.length > 1 ? (
home.gallery.slice(1, 5).map((g, i, arr) => {
const isLast = i === arr.length - 1;
const remaining = home.gallery.length - 5;
return (
openLightbox(i + 1)} style={{ cursor: 'zoom-in' }}>
{g.video ? (
<>
▶
>
) : (
)}
{isLast && remaining > 0 && (
+{remaining} photos
)}
);
})
) : (
<>
Exterior
Elevation
▶ Walkthrough
+28 photos
>
)}
{home.photosNotice && (
Note: {home.photosNotice}
)}
{/* Tabs */}
{tabs.map(t => (
setTab(t)}>{t}
))}
{/* Body */}
{home.location} · {home.status}
{home.name}
{(home.sqft || home.lotAcres) && (
{home.sqft ? `${home.sqft} Square Feet` : ''}{home.sqft && home.lotAcres ? ' · ' : ''}{home.lotAcres ? `${home.lotAcres} Acre Lot` : ''}
)}
{(home.beds || home.baths || home.sectionType) ? (
{home.beds && {home.beds} beds }
{home.beds && home.baths && · }
{home.baths && {home.baths} baths }
{home.sectionType && <>
·
{home.sectionType}
>}
) : (
Details coming soon
)}
{comingSoon &&
Details coming soon.
}
{tab === 'Photos' && !comingSoon &&
{description}
}
{tab === 'Floor Plans' && !comingSoon && (
{Array.isArray(home.floorPlans) && home.floorPlans.length > 0 ? (
{home.floorPlans.map((fp, i) => (
{fp.caption && {fp.caption} }
))}
) : (
)}
)}
{tab === 'Specs' && !comingSoon && (
{specRows.map(r => (
{r.label}
{r.value}
))}
)}
{tab === 'Photos' && !comingSoon && (
<>
Home Features
>
)}
{home.price ?
${home.price}
: home.priceLabel ?
{home.priceLabel}
: null}
setPreApprovalOpen(true)} className="btn btn-primary btn-sm">{home.ctaLabel || 'Inquire'}
{preApprovalOpen &&
setPreApprovalOpen(false)} />}
{tour3dOpen && home.tour3d && (
setTour3dOpen(false)} />
)}
{videoOpen && home.videoTour && (
setVideoOpen(false)} />
)}
{lightboxIdx !== null && Array.isArray(home.gallery) && home.gallery[lightboxIdx] && (
{ e.stopPropagation(); closeLightbox(); }} aria-label="Close">✕
{ e.stopPropagation(); lbPrev(); }} aria-label="Previous">‹
{ e.stopPropagation(); lbNext(); }} aria-label="Next">›
e.stopPropagation()} onTouchStart={onLbTouchStart} onTouchEnd={onLbTouchEnd}>
{home.gallery[lightboxIdx].video ? (
) : (
)}
{home.gallery[lightboxIdx].caption || `Photo ${lightboxIdx + 1}`}
{lightboxIdx + 1} / {galleryLen}
)}
);
}
// ── Apply / Inquire Modal ───────────────────────────────────
function PreApprovalModal({ home, onClose }) {
const ctaLabel = (home && home.ctaLabel) || 'Inquire';
// Auto-fill home/package field from the page/home the user is viewing
const defaultInterest = home
? `${home.name}${home.location ? ` — ${home.location}` : ''}`
: '';
const [submitted, setSubmitted] = useState(false);
const [data, setData] = useState({
fullName: '',
email: '',
phone: '',
interest: defaultInterest,
timeline: '',
notes: '',
consent: false,
});
const update = (k, v) => setData(prev => ({ ...prev, [k]: v }));
const canSubmit = data.fullName && data.email && data.phone && data.consent;
const submit = (e) => {
if (e) e.preventDefault();
if (!canSubmit) return;
sendLead('Inquiry — ' + ctaLabel, {
fullName: data.fullName, email: data.email, phone: data.phone,
interest: data.interest, timeline: data.timeline, notes: data.notes,
consent: data.consent ? 'Yes' : 'No',
});
setSubmitted(true);
};
return (
e.stopPropagation()}>
✕
{submitted ? (
✓
Thanks, {data.fullName.split(' ')[0] || 'there'}!
Your inquiry{data.interest ? <> for {data.interest} > : ''} is in. A New Liberty Homes specialist will reach out within one business day.
) : (
<>
{home && (home.name || home.location) && (
{ctaLabel}{home.name ? ` · ${home.name}` : ''}{home.location ? ` · ${home.location}` : ''}
)}
{ctaLabel}
Tell us a little about you and we'll be in touch within one business day. No credit check.
We'll never share your info.
{ctaLabel} →
>
)}
);
}
// ── Floor Plan SVG (procedural from bed/bath count) ─────────
function FloorPlanSVG({ home }) {
// Singlewide 16×76 default; scale lightly off sqft
const isSingle = (home.sectionType || '').toLowerCase().includes('single');
const W = isSingle ? 720 : 720;
const H = isSingle ? 170 : 280;
const stroke = '#13294B';
const wall = 3;
const fill = '#F5F4F1';
return (
{/* Outer shell */}
{/* Living / Dining / Kitchen (left half) */}
{/* Open archway gap */}
{/* Kitchen island */}
ISLAND
{/* Range + counter top wall */}
KITCHEN
LIVING / DINING
{/* Bathroom 2 (middle) */}
BATH 2
TUB
{/* Bedroom 2 + 3 (middle bottom) */}
BED 2
BED 3
{/* Utility / Hall */}
UTILITY · W/D
{/* Master suite (right) */}
PRIMARY
BEDROOM
{/* Master bath */}
PRIMARY BATH
SHWR
CLOSET
{/* Front door (bottom left) */}
FRONT
{/* Rear door */}
{/* Dimensions */}
76' 0"
15' 2"
);
}
// ── Homes Grid (with modal state) ───────────────────────────
function HomesGrid({ homes }) {
const [viewing, setViewing] = useState(null);
const [notifying, setNotifying] = useState(null);
React.useEffect(() => {
document.body.style.overflow = (viewing || notifying) ? 'hidden' : '';
return () => { document.body.style.overflow = ''; };
}, [viewing, notifying]);
return (
<>
{homes.map(h => (
))}
{viewing && setViewing(null)} />}
{notifying && setNotifying(null)} />}
>
);
}
// ── VIP Form ─────────────────────────────────────────────────
function VipForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [interest, setInterest] = useState('');
const [submitted, setSubmitted] = useState(false);
function handleSubmit(e) {
e.preventDefault();
sendLead('VIP List', { name, email, phone, interest });
setSubmitted(true);
}
if (submitted) {
return (
✓
You're on the VIP list!
We'll notify you the moment new homes match your interests. Watch your inbox.
);
}
return (
Get Early Access
Quick — under 30 seconds.
Full Name *
setName(e.target.value)} placeholder="Jane Doe" required />
I'm Interested In *
setInterest(e.target.value)} required>
Select…
Home + Land Packages
Building on My Land
Home Only (delivery)
Pre-Owned Homes
Owner Financing
Just Exploring
Join the VIP List
No spam. Unsubscribe any time.
);
}
// ── Multi-step Questionnaire ─────────────────────────────────
const QUIZ_STEPS = [
{ num: '01', title: 'Contact Info' },
{ num: '02', title: 'Buying Situation' },
{ num: '03', title: 'Property' },
{ num: '04', title: 'Home Preferences' },
{ num: '05', title: 'Financing' },
{ num: '06', title: 'Timeline' },
{ num: '07', title: 'How Did You Find Us?' },
];
function Questionnaire() {
const [step, setStep] = useState(0);
const [data, setData] = useState({
fullName: '', phone: '', email: '', contactMethod: 'Phone',
situation: '',
ownsLand: '', propertyLocation: '', utilities: [],
homeType: '', beds: '', baths: '', sqft: '', budget: '',
needsFinancing: '', ownerFinance: '', tradeIn: '',
timeline: '',
leadSource: '',
});
const [submitted, setSubmitted] = useState(false);
function update(k, v) { setData(prev => ({...prev, [k]: v})); }
function toggle(k, v) {
setData(prev => ({...prev, [k]: prev[k].includes(v) ? prev[k].filter(x=>x!==v) : [...prev[k], v]}));
}
function nextStep() {
if (step < QUIZ_STEPS.length - 1) setStep(step + 1);
else { sendLead('Questionnaire', data); setSubmitted(true); }
}
function prevStep() { if (step > 0) setStep(step - 1); }
function canAdvance() {
if (step === 0) return data.fullName && data.phone && data.email;
if (step === 1) return data.situation;
if (step === 5) return data.timeline;
return true;
}
if (submitted) {
return (
✓
Thank you, {data.fullName.split(' ')[0] || 'there'}!
We received your inquiry. A member of the New Liberty Homes team will reach out within one business day.
Browse Available Homes
);
}
const current = QUIZ_STEPS[step];
return (
Step {current.num} of {String(QUIZ_STEPS.length).padStart(2,'0')}
{current.title}
{QUIZ_STEPS.map((_, i) => (
))}
{step === 0 && (
Contact Info
Let's start with the basics
So we know how to reach you. We respect your privacy and never share your info.
Full Name *
update('fullName',e.target.value)} placeholder="Jane Doe" />
)}
{step === 1 && (
Buying Situation
What best describes you?
Pick whichever feels closest — we'll dial in the details together.
{[
{v:'Home + Land', d:'Move into a complete package'},
{v:'I Own Land', d:'Place a home on my property'},
{v:'Home Only', d:'Just need the home itself'},
{v:'Pre-Owned Home', d:'Affordable used options'},
{v:'Financing Options', d:'Need to figure out the money side'},
{v:'Just Exploring', d:'Still researching'},
].map(opt => (
update('situation',opt.v)} />
))}
)}
{step === 2 && (
Property
Tell us about the land
If you don't own land yet, no problem — we can help you find one too.
Property Location (city / county)
update('propertyLocation',e.target.value)} placeholder="e.g. Stephenville, TX" />
)}
{step === 3 && (
Home Preferences
Your dream home, in detail
These are starting points — we can adjust as we find the right fit.
Bedrooms
update('beds',e.target.value)}>
Select…
1 2 3 4 5+
Bathrooms
update('baths',e.target.value)}>
Select…
1 1.5 2 2.5 3+
Square Footage
update('sqft',e.target.value)}>
Select…
Under 900 900 – 1,200 1,200 – 1,600 1,600 – 2,000 2,000+
Budget Range
update('budget',e.target.value)}>
Select…
Under $75k $75k – $125k $125k – $175k $175k – $250k $250k+
)}
{step === 4 && (
Financing
How would you like to pay?
We work with multiple lenders and offer flexible owner-financing on select homes.
)}
{step === 5 && (
Timeline
When are you ready to move?
Knowing your timing helps us prioritize the right homes and financing options.
{[
{v:'ASAP', d:'Within a few weeks'},
{v:'30 Days', d:'About a month out'},
{v:'1–3 Months', d:'Plenty of runway'},
{v:'3–6 Months', d:'Planning ahead'},
{v:'Just Exploring', d:'No specific timeline yet'},
].map(opt => (
update('timeline',opt.v)} />
))}
)}
{step === 6 && (
One Last Thing
How did you find us?
Helps us serve you better — and know what's working.
{['Facebook','Google','QR Code','Referral','Drive-by Sign','Event','Other'].map(o => (
update('leadSource',o)} />
{o}
))}
)}
{step > 0 ? Step {step+1} of {QUIZ_STEPS.length} · {Math.round(((step)/QUIZ_STEPS.length)*100)}% complete : Takes about 2 minutes }
{step > 0 && (
← Back
)}
{step === QUIZ_STEPS.length - 1 ? 'Submit Inquiry' : 'Continue'}
);
}
// ── BUYER INQUIRY FORM ──────────────────────────────────────
// 4-step lead capture, used in the hero
const TX_LOCATIONS = [
'Stephenville','Dublin','Comanche','Kerrville','Granbury','Glen Rose','Hico','Hamilton',
'De Leon','Gorman','Mineral Wells','Weatherford','Brownwood','Early','Goldthwaite',
'Lampasas','Llano','Fredericksburg','Boerne','Bandera','Junction','Mason','San Saba',
'Erath County','Comanche County','Hood County','Somervell County','Hamilton County',
'Eastland County','Palo Pinto County','Parker County','Brown County','Mills County',
'Lampasas County','Llano County','Gillespie County','Kendall County','Kerr County',
'Bandera County','Kimble County','San Saba County','Mason County','Bosque County',
'Johnson County','Burnet County','Real County','Edwards County','Coryell County',
];
function BuyerInquiryForm() {
const [step, setStep] = useState(1); // 1..4
const [data, setData] = useState({
fullName: '', phone: '', email: '',
looking: [],
locations: [],
homeType: '', beds: '', baths: '', sqft: '', budget: '', timeline: '', consent: false,
});
const [submitted, setSubmitted] = useState(false);
const [locQuery, setLocQuery] = useState('');
const [locOpen, setLocOpen] = useState(false);
const update = (k, v) => setData(prev => ({ ...prev, [k]: v }));
const toggle = (k, v) => setData(prev => ({ ...prev, [k]: prev[k].includes(v) ? prev[k].filter(x => x !== v) : [...prev[k], v] }));
const canContinue1 = data.fullName && data.phone && data.email && data.consent;
const canContinue2 = data.looking.length > 0;
const canContinue3 = data.locations.length > 0;
const locSuggestions = locQuery.trim()
? TX_LOCATIONS.filter(l => l.toLowerCase().includes(locQuery.toLowerCase()) && !data.locations.includes(l)).slice(0, 8)
: TX_LOCATIONS.filter(l => !data.locations.includes(l)).slice(0, 8);
if (submitted) {
return (
✓
You're on the list, {data.fullName.split(' ')[0] || 'friend'}!
You'll be among the first to know when a home matching your preferences becomes available.
);
}
const Header = ({ title, sub }) => (
{sub &&
{sub}
}
{[1,2,3,4].map(n => (
))}
);
// ── STEP 1: Contact ──
if (step === 1) {
return (
Your information is kept confidential and will not be shared.
setStep(2)} disabled={!canContinue1} style={!canContinue1 ? {opacity:0.5, cursor:'not-allowed'} : {}}>Get Early Access →
);
}
// ── STEP 2: What are you looking for? ──
if (step === 2) {
const options = ['Home & Land Package','Home Only','Pre-Owned Home','Owner Financing','Other'];
return (
setStep(1)}>← Back
setStep(3)} disabled={!canContinue2} style={!canContinue2 ? {opacity:0.5, cursor:'not-allowed'} : {}}>Next →
);
}
// ── STEP 3: Location preferences ──
if (step === 3) {
return (
Search counties & cities (start typing)
{ setLocQuery(e.target.value); setLocOpen(true); }}
onFocus={() => setLocOpen(true)}
onBlur={() => setTimeout(() => setLocOpen(false), 180)}
placeholder="e.g. Stephenville, Erath County…"
/>
{locOpen && locSuggestions.length > 0 && (
{locSuggestions.map(s => (
{ e.preventDefault(); toggle('locations', s); setLocQuery(''); }}
>
{s}
+ Add
))}
)}
{data.locations.length > 0 && (
Selected ({data.locations.length})
{data.locations.map(l => (
{l}
toggle('locations', l)} aria-label={`Remove ${l}`}>×
))}
)}
setStep(2)}>← Back
setStep(4)} disabled={!canContinue3} style={!canContinue3 ? {opacity:0.5, cursor:'not-allowed'} : {}}>Next →
);
}
// ── STEP 4: Tell us about your ideal home ──
const Section = ({ title, children }) => (
{title}
{children}
);
return (
setStep(3)}>← Back
{ sendLead('Buyer Inquiry', data); setSubmitted(true); }}>Submit
);
}
Object.assign(window, { BrandMark, StatusBadge, HomeCard, HomesGrid, GetNotifiedModal, HomeDetailsModal, PreApprovalModal, VipForm, Questionnaire, BuyerInquiryForm });