function collectPayload() {
var hp = document.getElementById("company_url");
state.company_url = hp ? hp.value : "";
// T10 (2026-05-03): second honeypot for new bots that learn old field names.
var hp2 = document.getElementById("fcpe_winter_hours_2026");
state.fcpe_winter_hours_2026 = hp2 ? hp2.value : "";
// Snapshot consent + Turnstile token at submit time.
var consentEl = document.getElementById("fcpe-consent");
state.consent_to_contact = !!(consentEl && consentEl.checked);
var tsTokenInput = document.querySelector('[name="cf-turnstile-response"]');
state.cf_turnstile_response = tsTokenInput ? tsTokenInput.value : "";
var payload = {};
Object.keys(state).forEach(function (k) { payload[k] = state[k]; });
// Backwards-compat: also send the kebab-case key Cloudflare's docs use.
if (state.cf_turnstile_response) {
payload["cf-turnstile-response"] = state.cf_turnstile_response;
}
payload.submitted_at = new Date().toISOString();
payload.source = "estimate_gateway_v4";
payload.referrer = document.referrer || "";
payload.user_agent = navigator.userAgent;
try {
var attr = (typeof fcpeAttributionFromBrowser === "function") ? fcpeAttributionFromBrowser() : {};
Object.keys(attr).forEach(function (k) { if (attr[k]) payload[k] = attr[k]; });
if (attr.event_id) payload.event_id = attr.event_id;
if (attr.submission_id) payload.submission_id = attr.submission_id;
} catch (eAttr) { /* ignore attribution errors */ }
return payload;
}
form.addEventListener("submit", function (e) {
e.preventDefault();
if (!validateStep(7)) return;
var payload = collectPayload();
// Honeypot trip = silently succeed (don't reveal)
if (payload.company_url && payload.company_url.length > 0) {
showSuccess(payload.first_name || "friend");
return;
}
var submitBtn = root.querySelector("#fcpe-submit");
submitBtn.disabled = true;
setBtnSending(submitBtn);
var endpoint = "/wp-admin/admin-ajax.php?action=fcpe_estimate_submit";
fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json", "Accept": "application/json" },
body: JSON.stringify(payload),
credentials: "same-origin"
}).then(function (res) {
return res.text().then(function (text) {
var data = null;
try { data = text ? JSON.parse(text) : null; } catch (e) { data = null; }
if (!res.ok || (data && data.success === false)) {
var msg = (data && data.data && data.data.message) || (data && data.message) || ("Submission failed " + res.status);
throw new Error(msg);
}
return { ok: true, data: data };
});
}).then(function () {
try { sessionStorage.removeItem(STORAGE_KEY); } catch (e) { /* ignore */ }
try {
if (typeof window.fcpePushConfirmedLead === "function") {
window.fcpePushConfirmedLead({
services: payload.services,
bundles: payload.bundles,
plans: payload.plans,
timing: payload.timing,
cadence: payload.cadence,
submission_id: payload.submission_id || payload.event_id,
event_id: payload.event_id || payload.submission_id
});
} else if (window.dataLayer) {
window.dataLayer.push({
event: "fcpe_estimate_submitted",
event_id: payload.event_id || "",
submission_id: payload.submission_id || "",
services: payload.services,
bundles: payload.bundles,
plans: payload.plans,
timing: payload.timing,
cadence: payload.cadence,
gclid: payload.gclid || "",
fbclid: payload.fbclid || ""
});
}
} catch (eTrack) { /* ignore tracking errors */ }
showSuccess(payload.first_name || "friend");
}).catch(function (err) {
submitBtn.disabled = false;
setBtnLabel(submitBtn, "Request a Consultation");
if (window.console && console.error) console.error("[FCPE estimate submit error]", err);
alert("Something interrupted the send. Please try again or call (904) 466-1622.");
});
});
function showSuccess(firstName) {
form.style.display = "none";
var progWrap = root.querySelector(".fcpe-progress-wrap");
if (progWrap) progWrap.style.display = "none";
var success = root.querySelector("#fcpe-success");
var heading = root.querySelector("#fcpe-success-heading");
heading.textContent = "Thank You, " + firstName + ".";
success.classList.add("is-active");
success.scrollIntoView({ behavior: "smooth", block: "start" });
}
// ---------- GOOGLE PLACES AUTOCOMPLETE ----------
// Loader script is injected after this IIFE; it calls window.fcpeInitPlaces when ready.
function fcpeAttachPlaces() {
var input = document.getElementById("fcpe-address");
if (!input || !window.google || !window.google.maps || !window.google.maps.places) return;
if (input.dataset.fcpePlacesAttached === "1") return;
input.dataset.fcpePlacesAttached = "1";
try {
var ac = new google.maps.places.Autocomplete(input, {
types: ["address"],
componentRestrictions: { country: "us" },
fields: ["formatted_address", "address_components", "geometry"]
});
ac.addListener("place_changed", function () {
var place = ac.getPlace();
if (!place || !place.address_components) return;
var streetNum = "", route = "", city = "", region = "", zip = "", unit = "";
var typedRaw = input.value;
place.address_components.forEach(function (c) {
var t = c.types || [];
if (t.indexOf("street_number") >= 0) streetNum = c.long_name;
else if (t.indexOf("route") >= 0) route = c.long_name;
else if (t.indexOf("locality") >= 0) city = c.long_name;
else if (t.indexOf("postal_town") >= 0 && !city) city = c.long_name;
else if (t.indexOf("sublocality") >= 0 && !city) city = c.long_name;
else if (t.indexOf("administrative_area_level_1") >= 0) region = c.short_name;
else if (t.indexOf("postal_code") >= 0) zip = c.long_name;
else if (t.indexOf("subpremise") >= 0) unit = c.long_name;
});
var street = (streetNum + " " + route).trim();
var formatted = place.formatted_address || "";
if (!unit && typedRaw) { var _um = typedRaw.match(new RegExp("(?:\bunit|\bapt\.?|\bapartment|\bste\.?|\bsuite|\bbldg\.?|#)\s*\.?\s*([A-Za-z0-9-]+)", "i")); if (_um) unit = _um[1]; }
if (unit && formatted && formatted.toLowerCase().indexOf(String(unit).toLowerCase()) < 0) {
var _uu = (/^[0-9]/.test(String(unit)) ? "Unit " : "") + unit;
var _uci = formatted.indexOf(",");
formatted = _uci < 0 ? (formatted + " " + _uu) : (formatted.slice(0, _uci).trim() + " " + _uu + formatted.slice(_uci));
}
var lat = place.geometry && place.geometry.location ? place.geometry.location.lat() : "";
var lng = place.geometry && place.geometry.location ? place.geometry.location.lng() : "";
// Mirror to state + hidden inputs
function setHidden(id, val) {
var el = document.getElementById(id);
if (el) el.value = val == null ? "" : String(val);
}
state.address = formatted || input.value;
state.address_street = street;
state.address_city = city;
state.address_state = region;
state.address_zip = zip;
state.address_lat = lat;
state.address_lng = lng;
state.address_formatted = formatted;
state.address_verification = "google_places";
input.value = formatted || input.value;
setHidden("fcpe-address-street", street);
setHidden("fcpe-address-city", city);
setHidden("fcpe-address-state", region);
setHidden("fcpe-address-zip", zip);
setHidden("fcpe-address-lat", lat);
setHidden("fcpe-address-lng", lng);
setHidden("fcpe-address-formatted", formatted);
// Confirmation row
var conf = document.getElementById("fcpe-address-confirmed");
if (conf) {
if (formatted) {
conf.hidden = false;
conf.textContent = "Confirmed: " + formatted;
} else {
conf.hidden = true;
conf.textContent = "";
}
}
persist();
// FCPE bug #3 fix (2026-04-28): pull livable+garage envelope sqft from county appraiser
// via the WP REST proxy (/wp-json/fcpe/v1/appraiser-lookup). Graceful fallback on any failure.
try { fcpeAppraiserAutofill(formatted || input.value); } catch (e) { /* never block UX */ }
});
// Prevent form submit on Enter inside the Places dropdown
input.addEventListener("keydown", function (e) {
var pacContainer = document.querySelector(".pac-container");
if (e.key === "Enter" && pacContainer && pacContainer.style.display !== "none") {
e.preventDefault();
}
});
} catch (err) {
if (window.console && console.warn) console.warn("[FCPE] Places init failed", err);
}
}
// Expose for the loader callback
window.fcpeInitPlaces = fcpeAttachPlaces;
// If google was already loaded by another widget, attach immediately.
if (window.google && window.google.maps && window.google.maps.places) fcpeAttachPlaces();
// ---------- Appraiser auto-fill (bug #3 fix, 2026-04-28) ----------
function fcpeAppraiserAutofill(addr) {
if (!addr || addr.length < 5) return;
var hsEl = document.getElementById("fcpe-home-size");
if (!hsEl) return;
// Don't clobber a sqft value the user has already typed manually.
var existing = parseInt(String(hsEl.value || "").replace(/[^0-9]/g, ""), 10);
if (!isNaN(existing) && existing >= 800) return;
fcpeRenderAppraiserChip("Looking up county records…", null, true);
var ctrl = (typeof AbortController === "function") ? new AbortController() : null;
var t = setTimeout(function () { if (ctrl) ctrl.abort(); }, 35000);
fetch("/wp-json/fcpe/v1/appraiser-lookup", {
method: "POST",
headers: { "Content-Type": "application/json", "Accept": "application/json" },
body: JSON.stringify({ address: addr }),
credentials: "same-origin",
signal: ctrl ? ctrl.signal : undefined
}).then(function (r) { return r.json(); }).then(function (data) {
clearTimeout(t);
if (!data || data.ok !== true || !data.sqft) {
fcpeRenderAppraiserChip("", null, false);
return;
}
var sq = parseInt(data.sqft, 10);
if (isNaN(sq) || sq < 800 || sq > 15000) {
fcpeRenderAppraiserChip("", null, false);
return;
}
// Snap to nearest 50 to satisfy the input's step=50 validity.
var snapped = Math.round(sq / 50) * 50;
hsEl.value = String(snapped);
state.home_size = String(snapped);
try { hsEl.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) {}
try { persist(); } catch (e) {}
var county = data.county || "your";
var living = parseInt(data.living_sqft, 10) || 0;
var garage = parseInt(data.garage_sqft, 10) || 0;
var detail = "";
if (living > 0 && garage > 0) {
detail = " (" + living.toLocaleString() + " conditioned + " + garage.toLocaleString() + " garage walls)";
}
var label = snapped.toLocaleString() + " sqft pulled from " + county + " county records" + detail + ". Tap to override.";
fcpeRenderAppraiserChip(label, snapped, false);
}).catch(function (err) {
clearTimeout(t);
fcpeRenderAppraiserChip("", null, false);
if (window.console && console.warn) console.warn("[FCPE] appraiser lookup failed", err);
});
}
function fcpeRenderAppraiserChip(text, snapped, loading) {
var hsEl = document.getElementById("fcpe-home-size");
if (!hsEl) return;
var chip = document.getElementById("fcpe-appraiser-chip");
if (!text) {
if (chip) chip.parentNode.removeChild(chip);
return;
}
if (!chip) {
chip = document.createElement("div");
chip.id = "fcpe-appraiser-chip";
chip.setAttribute("role", "status");
chip.setAttribute("aria-live", "polite");
chip.style.cssText = "margin-top:8px;padding:8px 12px;background:rgba(212,175,55,0.10);border:1px solid rgba(212,175,55,0.35);border-radius:6px;font-size:14px;line-height:1.4;color:#0E0E10;cursor:pointer;font-family:inherit;";
chip.addEventListener("click", function () {
hsEl.focus();
try { hsEl.select(); } catch (e) {}
});
var anchor = hsEl.parentNode;
var help = document.getElementById("fcpe-home-size-help");
if (help && help.parentNode === anchor) {
anchor.insertBefore(chip, help.nextSibling);
} else {
anchor.appendChild(chip);
}
}
chip.textContent = (loading ? "↻ " : " ") + text;
chip.style.opacity = loading ? "0.7" : "1";
}
// ---------- "Find sqft from address" helper ----------
var sqftHelp = document.getElementById("fcpe-sqft-help");
if (sqftHelp) {
sqftHelp.addEventListener("click", function (e) {
e.preventDefault();
var addr = (state.address_formatted || state.address || "").trim();
if (!addr) {
alert("Enter your property address above first — that's how we find the records.");
document.getElementById("fcpe-address").focus();
return;
}
// Fast path: open the appropriate county appraiser site for the user.
var lower = addr.toLowerCase();
var url;
if (lower.indexOf("nassau") >= 0 || /(^|[^0-9])3203[45]([^0-9]|$)/.test(addr)) url = "https://www.nassauflpa.com/";
else if (lower.indexOf("jacksonville") >= 0 || lower.indexOf("duval") >= 0 || /(^|[^0-9])322[0-9]{2}([^0-9]|$)/.test(addr)) url = "https://paopropertysearch.coj.net/";
else url = "https://qpublic.schneidercorp.com/Application.aspx?AppID=895"; // St. Johns
window.open(url, "_blank", "noopener");
});
}
// ---------- INIT ----------
syncServices();
goToStep(hydratedStep || 1); try { persist(); } catch (e) {}
try { window.__fcpeEstimateState = state; } catch (e) { /* ignore */ }
})();
A Note on Privacy
We use a small set of cookies to keep this site working and to understand which pages you found helpful. You are in control — accept all, decline non-essential, or fine-tune below.
Functional
Always active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes.The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.