Residential Instant Quote

Tell us about you and your home for a quote in about 60 seconds from now.

If the price and avaiblity works for you book online right here!

/* Human readable summary for customers and staff */ function buildNotesBlob(inputs, total, hours){ const sizeLabel = (HOME_SIZE_RANGES.find(r=>r.value===inputs.size)||{}).label || inputs.size; const addOnList = ADDONS .map(([k,label]) => ( (INCLUDED_ADDONS[inputs.type]||new Set()).has(k) ? `${label} (Included)` : (inputs.addons[k] ? label : null))) .filter(Boolean) .join(", ") || "None"; const lines = [ `Quote: ${fmt(total)}`, `Cleaning: ${inputs.type==="standard" ? "Standard" : inputs.type==="moveout" ? "Move in or out" : inputs.type==="postconstruction" ? "Post construction" : "Deep"}`, `Size: ${sizeLabel}`, `Beds: ${inputs.beds} | Baths: ${inputs.baths}`, `Frequency: ${cap(inputs.frequency)}`, `Pets: ${inputs.petLevel}`, `Add-ons: ${addOnList}`, inputs.contact.address1 || inputs.contact.city || inputs.contact.zip ? `Service address: ${[ inputs.contact.address1, inputs.contact.address2, [inputs.contact.city, inputs.contact.state].filter(Boolean).join(", "), inputs.contact.zip ].filter(Boolean).join(" • ")}` : "", inputs.contact.gateCode ? `Gate or access: ${inputs.contact.gateCode}` : "", inputs.contact.notes ? `Client notes: ${inputs.contact.notes}` : "", `Estimated labor hours ≈ ${hours.toFixed(1)}` ].filter(Boolean); const plain = lines.join(" • ").slice(0, 1900); const html = lines .map(l => l.replace(/^([^:]+:)/, "$1")) .join("
"); return { plain, html }; } /* Build the calendar URL with robust field mapping */ function buildCalendarURL(base, inputs, total, hours){ const { plain, html } = buildNotesBlob(inputs, total, hours); const url = new URL(base); const p = url.searchParams; // Contact prefill (unchanged) const name = (inputs.contact.name||"").trim(); const parts = name.split(/\s+/); const first = parts[0] || ""; const last = parts.slice(1).join(" ") || ""; if (first) p.set("first_name", first); if (last) p.set("last_name", last); if (inputs.contact.email) p.set("email", inputs.contact.email); const phoneE164 = raw => { const d = String(raw||"").replace(/\D/g,""); if (d.length===10) return "+1"+d; if (d.length===11 && d.startsWith("1")) return "+"+d; return raw||""; }; const ph = phoneE164(inputs.contact.phone); if (ph){ p.set("phone", ph); p.set("phone_number", ph); } // Location const address = [ inputs.contact.address1||"", inputs.contact.address2||"", [inputs.contact.city||"", inputs.contact.state||""].filter(Boolean).join(", "), inputs.contact.zip||"" ].filter(Boolean).join(" • "); if (address){ p.set("location", address); p.set("address", address); p.set("appointment_location", address); } // Helper const writeBoth = (key, value) => { if (value == null) return; p.set(`custom_values[${key}]`, String(value)); p.set(key, String(value)); }; // Core hidden fields writeBoth("service_summary", plain); writeBoth("service_summary_html", html); // NEW: HTML-friendly version writeBoth("quoted_price", total); writeBoth("quoted_hours", hours.toFixed(1)); writeBoth("cleaning_type", inputs.type); writeBoth("home_size", inputs.size); writeBoth("beds", inputs.beds); writeBoth("baths", inputs.baths); writeBoth("frequency", inputs.frequency); writeBoth("pet_level", inputs.petLevel); const addonsSelected = Object.entries(inputs.addons || {}) .filter(([,v]) => v) .map(([k]) => k) .join(","); writeBoth("addons_selected", addonsSelected); // General description p.set("appointment_description", plain); p.set("description", plain); p.set("notes", plain); // UX p.set("prefill","true"); p.set("step","1"); p.set("tt", String(Date.now())); return url.toString(); }