Serving St. Johns, Duval, and Nassau counties. BBB A Rating. More than a century of combined expertise.
(function () {
“use strict”;
var root = document.getElementById(“fcpe-estimate-gateway”);
if (!root) return; var form = root.querySelector(“#fcpe-form”);
var steps = root.querySelectorAll(“.fcpe-step”);
var TOTAL_STEPS = 7;
var current = 1;
var STORAGE_KEY = “fcpe_estimate_v4_state”; var stepLabels = {
1: “What you need”,
2: “Your property”,
3: “Service specifics”,
4: “Bundle & save”,
5: “Timing”,
6: “Contact”,
7: “Review & submit”
}; // ———- STATE ———-
var state = {
services: [],
address: “”,
property_type: “”,
home_size: “”,
bedrooms: “”,
bathrooms: “”,
year_built: “”,
stories: “”,
roof_material: “”,
driveway_material: “”,
window_count: “”,
last_exterior: “”,
needs_gutters: false,
has_solar: false,
interior_frequency: “”,
pets: “”,
interior_focus: “”,
interior_concerns: [],
seal_surface: “”,
seal_sqft: “”,
last_sealed: “”,
joint_sand: “”,
commercial_type: “”,
commercial_sqft: “”,
visits_per_week: “”,
commercial_current: “”,
bundles: [],
plans: [],
addons: [],
timing: “”,
day_pref: “”,
time_pref: “”,
cadence: “”,
first_name: “”,
last_name: “”,
email: “”,
phone: “”,
contact_pref: “”,
requestor_role: “”,
notes: “”,
client_portal: true,
company_url: “”
}; // Hydrate from sessionStorage if available
var hydratedStep = 0;
try {
var raw = sessionStorage.getItem(STORAGE_KEY);
if (raw) {
var parsed = JSON.parse(raw);
if (parsed && typeof parsed === “object”) {
Object.keys(parsed).forEach(function (k) {
if (k in state) state[k] = parsed[k];
});
if (typeof parsed.__step === “number” && parsed.__step >= 1 && parsed.__step <= TOTAL_STEPS) {
hydratedStep = parsed.__step;
}
}
}
} catch (e) { /* ignore */ } function persist() {
try {
var snapshot = {};
Object.keys(state).forEach(function (k) { snapshot[k] = state[k]; });
snapshot.__step = current;
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(snapshot));
} catch (e) { /* quota, private mode */ }
} function clearChildren(node) {
while (node && node.firstChild) node.removeChild(node.firstChild);
} // ———- ELEMENTS ———-
var progressFill = root.querySelector("#fcpe-progress-fill");
var stepNumEl = root.querySelector("#fcpe-step-num");
var stepLabelEl = root.querySelector("#fcpe-step-label");
var progressBar = root.querySelector(".fcpe-progress-track"); // ———- STEP NAV ———-
function goToStep(n) {
if (n TOTAL_STEPS) return;
if (n === 3) updateSpecificsVisibility();
if (n === 4) buildBundles();
if (n === 7) buildReview(); steps.forEach(function (s) { s.classList.remove(“is-active”); });
var next = root.querySelector(‘.fcpe-step[data-step=”‘ + n + ‘”]’);
if (!next) return;
next.style.opacity = “0”;
next.classList.add(“is-active”);
requestAnimationFrame(function () { next.style.opacity = “1”; }); current = n;
var pct = (n / TOTAL_STEPS) * 100;
progressFill.style.width = pct + “%”;
progressBar.setAttribute(“aria-valuenow”, String(n));
stepNumEl.textContent = String(n);
stepLabelEl.textContent = stepLabels[n] || “”; var legend = next.querySelector(“legend”);
if (legend) {
legend.setAttribute(“tabindex”, “-1”);
try { legend.focus({ preventScroll: true }); } catch (e) { legend.focus(); }
} var wrap = root.querySelector(“.fcpe-progress-wrap”);
if (wrap && window.innerWidth 0;
continueBtnS1.disabled = !ok;
var errEl = root.querySelector(“#err-services”);
if (errEl && ok) errEl.style.display = “none”;
persist();
} // ———- PILL GROUPS (single + multi) ———-
function bindPillGroup(group) {
var single = group.getAttribute(“data-single”);
var multi = group.getAttribute(“data-multi”);
var pills = group.querySelectorAll(“.fcpe-pill”); // hydrate
pills.forEach(function (pill) {
var inp = pill.querySelector(“input”);
if (!inp) return;
if (single && state[single] === inp.value) {
inp.checked = true;
pill.classList.add(“is-selected”);
}
if (multi && Array.isArray(state[multi]) && state[multi].indexOf(inp.value) !== -1) {
inp.checked = true;
pill.classList.add(“is-selected”);
}
}); pills.forEach(function (pill) {
var inp = pill.querySelector(“input”);
if (!inp) return;
pill.addEventListener(“click”, function (e) {
if (e.target !== inp) {
e.preventDefault();
if (single) {
inp.checked = true;
} else {
inp.checked = !inp.checked;
}
}
if (single) {
group.querySelectorAll(“.fcpe-pill”).forEach(function (p) { p.classList.remove(“is-selected”); });
pill.classList.add(“is-selected”);
state[single] = inp.value;
} else if (multi) {
pill.classList.toggle(“is-selected”, inp.checked);
var cur = Array.isArray(state[multi]) ? state[multi].slice() : [];
var idx = cur.indexOf(inp.value);
if (inp.checked && idx === -1) cur.push(inp.value);
if (!inp.checked && idx !== -1) cur.splice(idx, 1);
state[multi] = cur;
}
persist();
});
});
} root.querySelectorAll(“.fcpe-pill-group[data-single], .fcpe-pill-group[data-multi]”).forEach(bindPillGroup); // ———- CHECK ROWS (gutters, solar, portal, addons) ———-
function bindCheck(row, key, isMulti) {
var inp = row.querySelector(“input”);
if (!inp) return;
if (isMulti) {
if (Array.isArray(state[key]) && state[key].indexOf(inp.value) !== -1) {
inp.checked = true;
row.classList.add(“is-selected”);
}
} else {
if (state[key]) {
inp.checked = true;
row.classList.add(“is-selected”);
}
}
row.addEventListener(“click”, function (e) {
if (e.target !== inp) {
e.preventDefault();
inp.checked = !inp.checked;
}
row.classList.toggle(“is-selected”, inp.checked);
if (isMulti) {
var cur = Array.isArray(state[key]) ? state[key].slice() : [];
var idx = cur.indexOf(inp.value);
if (inp.checked && idx === -1) cur.push(inp.value);
if (!inp.checked && idx !== -1) cur.splice(idx, 1);
state[key] = cur;
} else {
state[key] = !!inp.checked;
}
persist();
});
} var gutterRow = root.querySelector(‘[data-toggle=”gutters”]’);
if (gutterRow) bindCheck(gutterRow, “needs_gutters”, false);
var solarRow = root.querySelector(‘[data-toggle=”solar”]’);
if (solarRow) bindCheck(solarRow, “has_solar”, false); root.querySelectorAll(“[data-addon]”).forEach(function (row) { bindCheck(row, “addons”, true); }); var portalCheck = root.querySelector(“#fcpe-portal-check”);
if (portalCheck) {
var portalInput = portalCheck.querySelector(“input”);
if (typeof state.client_portal === “undefined”) state.client_portal = true;
portalInput.checked = !!state.client_portal;
portalCheck.classList.toggle(“is-selected”, portalInput.checked);
portalCheck.addEventListener(“click”, function (e) {
if (e.target !== portalInput) {
e.preventDefault();
portalInput.checked = !portalInput.checked;
}
portalCheck.classList.toggle(“is-selected”, portalInput.checked);
state.client_portal = !!portalInput.checked;
persist();
});
} // ———- TEXT / SELECT INPUTS (generic bind) ———-
var simpleBindings = [
“fcpe-address:address”,
“fcpe-bedrooms:bedrooms”,
“fcpe-bathrooms:bathrooms”,
“fcpe-year-built:year_built”,
“fcpe-windows:window_count”,
“fcpe-seal-sqft:seal_sqft”,
“fcpe-commercial-sqft:commercial_sqft”,
“fcpe-visits:visits_per_week”,
“fcpe-first:first_name”,
“fcpe-last:last_name”,
“fcpe-email:email”,
“fcpe-phone:phone”,
“fcpe-notes:notes”
];
simpleBindings.forEach(function (map) {
var parts = map.split(“:”);
var el = document.getElementById(parts[0]);
var key = parts[1];
if (!el) return;
if (state[key]) el.value = state[key];
el.addEventListener(“input”, function () {
state[key] = el.value;
persist();
});
el.addEventListener(“change”, function () {
state[key] = el.value;
persist();
});
}); // ———- PHONE AUTO-FORMAT ———-
var phoneEl = document.getElementById(“fcpe-phone”);
if (phoneEl) {
phoneEl.addEventListener(“input”, function () {
var raw = phoneEl.value.replace(/D/g, “”).slice(0, 10);
var out = raw;
if (raw.length > 6) out = “(” + raw.slice(0, 3) + “) ” + raw.slice(3, 6) + “-” + raw.slice(6);
else if (raw.length > 3) out = “(” + raw.slice(0, 3) + “) ” + raw.slice(3);
else if (raw.length > 0) out = “(” + raw;
phoneEl.value = out;
state.phone = out;
persist();
});
} // ———- SPECIFICS VISIBILITY (step 3) ———-
function updateSpecificsVisibility() {
var blocks = root.querySelectorAll(“[data-svc-block]”);
var anyShown = false;
blocks.forEach(function (block) {
var svc = block.getAttribute(“data-svc-block”);
var show = state.services.indexOf(svc) !== -1;
block.style.display = show ? “block” : “none”;
if (show) anyShown = true;
});
var errEl = root.querySelector(“#err-specifics”);
if (errEl) errEl.style.display = anyShown ? “none” : “block”;
} // ———- BUNDLE + PLAN CATALOGS ———-
function bundleCatalog() {
var svc = state.services;
var hasInt = svc.indexOf(“interior”) !== -1;
var hasExt = svc.indexOf(“exterior”) !== -1;
var hasSeal = svc.indexOf(“sealing”) !== -1; var bundles = [];
if (hasInt && hasExt) {
bundles.push({
id: “full_property”,
badge: “Save 8%”,
title: “Full Property Care Bundle”,
desc: “Interior housekeeping plus exterior soft-wash on one unified schedule. Our highest-value pairing.”,
save: “Save 8% on combined visits.”
});
}
if (hasExt && hasSeal) {
bundles.push({
id: “outdoor_complete”,
badge: “Save 6%”,
title: “Outdoor Complete”,
desc: “Exterior soft-wash paired with sanding and sealing — one crew, one visit window, one invoice.”,
save: “Save 6% when booked together.”
});
}
if (hasInt && !hasExt && !hasSeal) {
bundles.push({
id: “window_interior_add”,
badge: “Save 12%”,
title: “Add Interior Window Cleaning”,
desc: “Pair with any recurring housekeeping visit. Streak-free, pane by pane.”,
save: “Save 12% on window cleaning.”
});
}
if (hasExt && !hasInt && !hasSeal) {
bundles.push({
id: “gutter_whitening_bundle”,
badge: “Save 15%”,
title: “Add Gutter Whitening”,
desc: “Bolt-on to any soft-wash visit. Gutter faces restored to bright white.”,
save: “15% off when bundled with soft-wash.”
});
}
if (hasSeal && !hasInt && !hasExt) {
bundles.push({
id: “walkway_patio_seal”,
badge: “Estate Coverage”,
title: “Add Walkway + Patio Sealing”,
desc: “Extend your sealing scope to walkways and patio surfaces for a complete hardscape package.”,
save: “Estate-level coverage — one trip, full yard.”
});
}
if (bundles.length === 0) {
bundles.push({
id: “custom_scope”,
badge: “Custom”,
title: “Custom Scope Proposal”,
desc: “We’ll build a tailored scope around your property — no forced bundles, no fluff.”,
save: “Priced to your exact needs.”
});
}
return bundles.slice(0, 3);
} function planCatalog() {
var svc = state.services;
var hasInt = svc.indexOf(“interior”) !== -1;
var hasExt = svc.indexOf(“exterior”) !== -1;
var hasSeal = svc.indexOf(“sealing”) !== -1; var plans = [];
if (hasInt) {
plans.push({
id: “crystal_clear”,
badge: “Interior”,
title: “Crystal Clear”,
desc: “Biweekly interior housekeeping, same team every visit. Priority scheduling and 5% off any exterior service.”,
save: “Starts at $340/mo for a 2,000 sqft home.”
});
}
if (hasInt && hasExt) {
plans.push({
id: “gold_standard”,
badge: “Flagship”,
title: “Gold Standard Plan”,
desc: “Monthly interior visits plus quarterly exterior care. Our flagship membership — includes a free Gold Standard Deep Clean.”,
save: “Save 8% on combined visits.”
});
}
if (hasExt) {
plans.push({
id: “solar_shine”,
badge: “Exterior”,
title: “Solar Shine”,
desc: “Quarterly exterior soft-wash plus solar panel cleaning. Keeps your panels producing and your paint lasting.”,
save: “Year-round protection on a custom schedule.”
});
}
if (hasSeal) {
plans.push({
id: “hardscape”,
badge: “Sealing”,
title: “Hardscape Plan”,
desc: “Annual sanding and sealing with joint angular sand. Travertine, paver, and stone — priced to protect.”,
save: “Custom-priced to your surface type and size.”
});
}
if (hasInt) {
plans.push({
id: “estate_interior”,
badge: “Premium”,
title: “Estate Interior”,
desc: “Weekly interior housekeeping, dedicated concierge, same-team guarantee. Built for estates and entertainers.”,
save: “Priority booking, 48-hour response.”
});
}
if (hasExt) {
plans.push({
id: “estate_exterior”,
badge: “Premium”,
title: “Estate Exterior”,
desc: “Monthly exterior touch-ups with seasonal deep resets. The most thorough exterior care we offer.”,
save: “Includes gutter whitening and screen clean.”
});
}
if (plans.length === 0) {
plans.push({
id: “custom_plan”,
badge: “Custom”,
title: “Custom Plan Proposal”,
desc: “We’ll recommend the plan structure that fits your property on the walk-through.”,
save: “No commitment until you see the numbers.”
});
}
return plans.slice(0, 4);
} function buildBundles() {
var bundleHost = root.querySelector(“#fcpe-bundle-host”);
clearChildren(bundleHost);
var bundles = bundleCatalog();
bundleHost.classList.toggle(“has-three”, bundles.length >= 3);
bundles.forEach(function (cfg) {
bundleHost.appendChild(renderSelectCard(cfg, “bundles”));
}); var planHost = root.querySelector(“#fcpe-plan-host”);
clearChildren(planHost);
var plans = planCatalog();
planHost.classList.toggle(“has-three”, plans.length >= 3);
plans.forEach(function (cfg) {
planHost.appendChild(renderSelectCard(cfg, “plans”));
});
} function renderSelectCard(cfg, stateKey) {
var label = document.createElement(“label”);
label.className = “fcpe-bundle-card”;
label.setAttribute(“data-bundle-id”, cfg.id); var inp = document.createElement(“input”);
inp.type = “checkbox”;
inp.name = stateKey;
inp.value = cfg.id;
if (Array.isArray(state[stateKey]) && state[stateKey].indexOf(cfg.id) !== -1) {
inp.checked = true;
label.classList.add(“is-selected”);
}
label.appendChild(inp); var tick = document.createElement(“span”);
tick.className = “tick”;
tick.setAttribute(“aria-hidden”, “true”);
label.appendChild(tick); var badge = document.createElement(“span”);
badge.className = “badge”;
badge.textContent = cfg.badge;
label.appendChild(badge); var h4 = document.createElement(“h4”);
h4.textContent = cfg.title;
label.appendChild(h4); var desc = document.createElement(“p”);
desc.className = “desc”;
desc.textContent = cfg.desc;
label.appendChild(desc); var save = document.createElement(“p”);
save.className = “save”;
save.textContent = cfg.save;
label.appendChild(save); label.addEventListener(“click”, function (e) {
if (e.target !== inp) {
e.preventDefault();
inp.checked = !inp.checked;
}
label.classList.toggle(“is-selected”, inp.checked);
var cur = Array.isArray(state[stateKey]) ? state[stateKey].slice() : [];
var idx = cur.indexOf(cfg.id);
if (inp.checked && idx === -1) cur.push(cfg.id);
if (!inp.checked && idx !== -1) cur.splice(idx, 1);
state[stateKey] = cur;
persist();
});
label.tabIndex = 0;
label.setAttribute(“role”, “checkbox”);
label.setAttribute(“aria-checked”, inp.checked ? “true” : “false”);
label.addEventListener(“keydown”, function (e) {
if (e.key === ” ” || e.key === “Enter”) {
e.preventDefault();
inp.checked = !inp.checked;
label.classList.toggle(“is-selected”, inp.checked);
label.setAttribute(“aria-checked”, inp.checked ? “true” : “false”);
var cur = Array.isArray(state[stateKey]) ? state[stateKey].slice() : [];
var idx = cur.indexOf(cfg.id);
if (inp.checked && idx === -1) cur.push(cfg.id);
if (!inp.checked && idx !== -1) cur.splice(idx, 1);
state[stateKey] = cur;
persist();
}
}); return label;
} // ———- REVIEW LABELS ———-
var LABELS = {
services: { exterior: “Exterior care”, interior: “Interior housekeeping”, sealing: “Sanding & sealing”, commercial: “Commercial” },
property_type: { single_family: “Single-family”, townhome: “Townhome”, condo: “Condo”, estate: “Estate”, commercial: “Commercial” },
home_size: { under_1500: “Under 1,500 sqft”, “1500_2500”: “1,500–2,500 sqft”, “2500_4000”: “2,500–4,000 sqft”, “4000_6000”: “4,000–6,000 sqft”, “6000_plus”: “6,000+ sqft” },
stories: { “1”: “1 story”, “2”: “2 stories”, “3_plus”: “3+ stories” },
roof_material: { shingle: “Asphalt shingle”, tile: “Tile”, metal: “Metal”, other: “Other” },
driveway_material: { concrete: “Concrete”, paver: “Paver”, travertine: “Travertine”, stamped: “Stamped”, other: “Other” },
window_count: { under_10: “Under 10”, “10_20”: “10–20”, “20_35”: “20–35”, “35_plus”: “35+” },
last_exterior: { never: “Never”, over_1yr: “Over 1 year”, under_1yr: “Under 1 year” },
interior_frequency: { weekly: “Weekly”, biweekly: “Biweekly”, monthly: “Monthly”, one_time: “One-time”, never: “No prior service” },
pets: { none: “None”, “1”: “1 pet”, “2”: “2 pets”, “3_plus”: “3+ pets” },
interior_focus: { deep_clean: “Deep clean”, maintenance: “Maintenance”, move_in_out: “Move-in/out”, rental_turn: “Vacation rental turn” },
interior_concerns: { baseboards: “Baseboards”, inside_oven: “Inside oven”, inside_fridge: “Inside fridge”, windows_interior: “Interior windows”, blinds: “Blinds”, cabinets: “Cabinets” },
seal_surface: { travertine: “Travertine”, paver: “Paver”, concrete: “Concrete”, natural_stone: “Natural stone”, other: “Other” },
last_sealed: { never: “Never”, over_3yr: “Over 3 years”, “1_3yr”: “1–3 years”, under_1yr: “Under 1 year” },
joint_sand: { intact: “Intact”, loose: “Loose”, missing: “Missing”, unsure: “Unsure” },
commercial_type: { office: “Office”, hoa: “HOA”, restaurant: “Restaurant”, storefront: “Storefront”, medical: “Medical”, industrial: “Industrial” },
visits_per_week: { “1”: “1x / week”, “2”: “2x / week”, “3”: “3x / week”, “5”: “5x / week”, “7”: “Daily”, as_needed: “As needed” },
commercial_current: { yes: “Yes — considering a change”, no: “No — first time hiring” },
timing: { asap: “ASAP”, “2_weeks”: “Within 2 weeks”, this_month: “This month”, “1_3_months”: “1–3 months”, planning: “Just planning” },
day_pref: { weekday: “Weekday”, weekend: “Weekend”, either: “Either” },
time_pref: { am: “AM”, pm: “PM”, either: “Either” },
cadence: { one_time: “One-time”, recurring: “Recurring (plan)”, undecided: “Undecided” },
contact_pref: { call: “Call”, text: “Text”, email: “Email” },
requestor_role: { owner: “Property owner”, property_manager: “Property manager”, realtor: “Realtor”, other: “Other” },
addons: { gutter_cleaning: “Gutter cleaning (from $125)”, gutter_whitening: “Gutter whitening (from $100/side)”, flatwork: “Driveway & sidewalk wash (from $175)”, pool_deck: “Pool deck cleaning (from $225)”, outdoor_kitchen: “Outdoor kitchen & lanai (from $125)”, carpet_cleaning: “Carpet cleaning HWE (from $165)”, screen_cleaning: “Screen cleaning (from $125)”, solar_panel: “Solar panel cleaning (from $185)” }
}; function lookupLabel(key, val) {
if (!val) return “—”;
if (LABELS[key] && LABELS[key][val]) return LABELS[key][val];
return val;
}
function lookupList(key, vals) {
if (!Array.isArray(vals) || !vals.length) return “—”;
return vals.map(function (v) { return lookupLabel(key, v); }).join(“, “);
} function buildReview() {
var host = root.querySelector(“#fcpe-review-host”);
clearChildren(host); var sections = []; sections.push({
step: 1,
title: “What you need”,
body: lookupList(“services”, state.services)
}); var propBits = [];
if (state.address) propBits.push(state.address);
if (state.property_type) propBits.push(lookupLabel(“property_type”, state.property_type));
if (state.home_size) propBits.push(lookupLabel(“home_size”, state.home_size));
if (state.bedrooms) propBits.push(state.bedrooms + ” bed”);
if (state.bathrooms) propBits.push(state.bathrooms + ” bath”);
if (state.stories) propBits.push(lookupLabel(“stories”, state.stories));
if (state.year_built) propBits.push(“Built ” + state.year_built);
sections.push({
step: 2,
title: “Your property”,
body: propBits.length ? propBits.join(” · “) : “—”
}); var specificsItems = [];
if (state.services.indexOf(“exterior”) !== -1) {
var ext = [];
if (state.roof_material) ext.push(“Roof: ” + lookupLabel(“roof_material”, state.roof_material));
if (state.driveway_material) ext.push(“Driveway: ” + lookupLabel(“driveway_material”, state.driveway_material));
if (state.window_count) ext.push(“Windows: ” + lookupLabel(“window_count”, state.window_count));
if (state.last_exterior) ext.push(“Last clean: ” + lookupLabel(“last_exterior”, state.last_exterior));
if (state.needs_gutters) ext.push(“Gutter cleaning: yes”);
if (state.has_solar) ext.push(“Solar panels: yes”);
if (ext.length) specificsItems.push({ h: “Exterior”, lines: ext });
}
if (state.services.indexOf(“interior”) !== -1) {
var intArr = [];
if (state.interior_frequency) intArr.push(“Frequency: ” + lookupLabel(“interior_frequency”, state.interior_frequency));
if (state.pets) intArr.push(“Pets: ” + lookupLabel(“pets”, state.pets));
if (state.interior_focus) intArr.push(“Focus: ” + lookupLabel(“interior_focus”, state.interior_focus));
if (state.interior_concerns && state.interior_concerns.length) intArr.push(“Concerns: ” + lookupList(“interior_concerns”, state.interior_concerns));
if (intArr.length) specificsItems.push({ h: “Interior”, lines: intArr });
}
if (state.services.indexOf(“sealing”) !== -1) {
var sArr = [];
if (state.seal_surface) sArr.push(“Surface: ” + lookupLabel(“seal_surface”, state.seal_surface));
if (state.seal_sqft) sArr.push(“Approx sqft: ” + state.seal_sqft);
if (state.last_sealed) sArr.push(“Last sealed: ” + lookupLabel(“last_sealed”, state.last_sealed));
if (state.joint_sand) sArr.push(“Joint sand: ” + lookupLabel(“joint_sand”, state.joint_sand));
if (sArr.length) specificsItems.push({ h: “Sanding & sealing”, lines: sArr });
}
if (state.services.indexOf(“commercial”) !== -1) {
var cArr = [];
if (state.commercial_type) cArr.push(“Type: ” + lookupLabel(“commercial_type”, state.commercial_type));
if (state.commercial_sqft) cArr.push(“Sqft: ” + state.commercial_sqft);
if (state.visits_per_week) cArr.push(“Visits: ” + lookupLabel(“visits_per_week”, state.visits_per_week));
if (state.commercial_current) cArr.push(“Current provider: ” + lookupLabel(“commercial_current”, state.commercial_current));
if (cArr.length) specificsItems.push({ h: “Commercial”, lines: cArr });
}
sections.push({
step: 3,
title: “Service specifics”,
specificsItems: specificsItems
}); var b4 = [];
if (state.bundles && state.bundles.length) b4.push(“Bundles: ” + state.bundles.join(“, “));
if (state.plans && state.plans.length) b4.push(“Plans: ” + lookupList(“plans”, state.plans));
if (state.addons && state.addons.length) b4.push(“Add-ons: ” + lookupList(“addons”, state.addons));
sections.push({
step: 4,
title: “Bundles, plans & add-ons”,
body: b4.length ? b4.join(” · “) : “None selected — we’ll propose options.”
}); var tBits = [];
if (state.timing) tBits.push(lookupLabel(“timing”, state.timing));
if (state.day_pref) tBits.push(lookupLabel(“day_pref”, state.day_pref));
if (state.time_pref) tBits.push(lookupLabel(“time_pref”, state.time_pref));
if (state.cadence) tBits.push(lookupLabel(“cadence”, state.cadence));
sections.push({
step: 5,
title: “Timing”,
body: tBits.length ? tBits.join(” · “) : “—”
}); var contactNode = document.createElement(“span”);
contactNode.className = “fcpe-review-contact-line”;
var hasContactBits = false;
function appendContactBit(text, options) {
if (!text) return;
if (hasContactBits) {
var sep = document.createElement(“span”);
sep.className = “fcpe-review-sep”;
sep.textContent = ” u00b7 “;
contactNode.appendChild(sep);
}
var span = document.createElement(“span”);
if (options && options.noSwap) {
span.className = “nsnc”;
span.setAttribute(“data-callrail-preserve”, “true”);
span.setAttribute(“translate”, “no”);
}
span.textContent = text;
contactNode.appendChild(span);
hasContactBits = true;
}
var fullName = [state.first_name, state.last_name].filter(Boolean).join(” “);
appendContactBit(fullName);
appendContactBit(state.email);
if (state.phone) appendContactBit(state.phone, { noSwap: true });
if (state.contact_pref) appendContactBit(“Prefers: ” + lookupLabel(“contact_pref”, state.contact_pref));
if (state.requestor_role) appendContactBit(“Role: ” + lookupLabel(“requestor_role”, state.requestor_role));
if (state.client_portal) appendContactBit(“Portal: yes”);
if (state.notes) appendContactBit(“Notes: ” + state.notes);
sections.push({
step: 6,
title: “Contact”,
customNode: hasContactBits ? contactNode : null
}); sections.forEach(function (sec) {
var wrap = document.createElement(“div”);
wrap.className = “fcpe-review-section”; var head = document.createElement(“div”);
head.className = “fcpe-review-head”;
var h = document.createElement(“h4”);
h.textContent = sec.title;
head.appendChild(h); var editBtn = document.createElement(“button”);
editBtn.type = “button”;
editBtn.className = “fcpe-review-edit”;
editBtn.textContent = “Edit”;
editBtn.setAttribute(“aria-label”, “Edit ” + sec.title);
editBtn.addEventListener(“click”, function () { goToStep(sec.step); });
head.appendChild(editBtn);
wrap.appendChild(head); var body = document.createElement(“div”);
body.className = “fcpe-review-body”;
if (sec.customNode) {
body.appendChild(sec.customNode);
wrap.appendChild(body);
host.appendChild(wrap);
return;
}
if (sec.specificsItems && sec.specificsItems.length) {
sec.specificsItems.forEach(function (item) {
var sh = document.createElement(“div”);
sh.className = “spec-block”;
var strong = document.createElement(“strong”);
strong.textContent = item.h;
sh.appendChild(strong);
var ul = document.createElement(“ul”);
item.lines.forEach(function (line) {
var li = document.createElement(“li”);
li.textContent = line;
ul.appendChild(li);
});
sh.appendChild(ul);
body.appendChild(sh);
});
} else if (sec.specificsItems) {
var mtd = document.createElement(“span”);
mtd.className = “muted”;
mtd.textContent = “—”;
body.appendChild(mtd);
} else {
body.textContent = sec.body || “—”;
}
wrap.appendChild(body); host.appendChild(wrap);
});
} // ———- VALIDATION ———-
var EMAIL_RE = /^[^s@]+@[^s@]+.[^s@]+$/;
var PHONE_RE = /^(d{3})sd{3}-d{4}$/; function showErr(el, show) {
if (!el) return;
var field = el.closest(“.fcpe-field”) || el.closest(“fieldset”);
if (field) field.classList.toggle(“is-invalid”, !!show);
var err = field ? field.querySelector(“.fcpe-err”) : null;
if (err) err.classList.toggle(“is-visible”, !!show);
} function showErrForField(fieldEl, show) {
if (!fieldEl) return;
fieldEl.classList.toggle(“is-invalid”, !!show);
var err = fieldEl.querySelector(“.fcpe-err”);
if (err) err.classList.toggle(“is-visible”, !!show);
} function validateStep(n) {
if (n === 1) {
var ok1 = state.services.length > 0;
var err1 = root.querySelector(“#err-services”);
if (err1) err1.style.display = ok1 ? “none” : “block”;
return ok1;
}
if (n === 2) {
var addrEl = document.getElementById(“fcpe-address”);
var addrOk = state.address.trim().length >= 5;
showErr(addrEl, !addrOk); var ptGroup = root.querySelector(‘[data-single=”property_type”]’).closest(“.fcpe-field”);
var ptOk = !!state.property_type;
showErrForField(ptGroup, !ptOk); var hsGroup = root.querySelector(‘[data-single=”home_size”]’).closest(“.fcpe-field”);
var hsOk = !!state.home_size;
showErrForField(hsGroup, !hsOk); return addrOk && ptOk && hsOk;
}
if (n === 3) {
var err3 = root.querySelector(“#err-specifics”);
var ok3 = state.services.length > 0;
if (err3) err3.style.display = ok3 ? “none” : “block”;
return ok3;
}
if (n === 4) { return true; }
if (n === 5) {
var tGroup = root.querySelector(‘[data-single=”timing”]’).closest(“.fcpe-field”);
var tOk = !!state.timing;
showErrForField(tGroup, !tOk);
return tOk;
}
if (n === 6) {
var fn = document.getElementById(“fcpe-first”);
var ln = document.getElementById(“fcpe-last”);
var em = document.getElementById(“fcpe-email”);
var ph = document.getElementById(“fcpe-phone”);
var fnOk = state.first_name.trim().length > 0;
var lnOk = state.last_name.trim().length > 0;
var emOk = EMAIL_RE.test(state.email.trim());
var phOk = PHONE_RE.test(state.phone.trim()) || state.phone.replace(/D/g, “”).length === 10;
showErr(fn, !fnOk);
showErr(ln, !lnOk);
showErr(em, !emOk);
showErr(ph, !phOk);
return fnOk && lnOk && emOk && phOk;
}
if (n === 7) { return validateStep(6); }
return true;
} // ———- NAV BUTTON HANDLERS ———-
root.querySelectorAll(“[data-action]”).forEach(function (btn) {
btn.addEventListener(“click”, function () {
var action = btn.getAttribute(“data-action”);
if (action === “next”) {
if (!validateStep(current)) return;
goToStep(current + 1);
} else if (action === “back”) {
goToStep(current – 1);
}
});
}); // ———- SUBMIT ———-
function setBtnSending(btn) {
var labelEl = btn.querySelector(“#fcpe-submit-label”);
if (!labelEl) return;
clearChildren(labelEl);
var spin = document.createElement(“span”);
spin.className = “fcpe-spinner”;
spin.setAttribute(“aria-hidden”, “true”);
labelEl.appendChild(spin);
labelEl.appendChild(document.createTextNode(“Sending…”));
}
function setBtnLabel(btn, text) {
var labelEl = btn.querySelector(“#fcpe-submit-label”);
if (!labelEl) return;
clearChildren(labelEl);
labelEl.appendChild(document.createTextNode(text));
} function collectPayload() {
var hp = document.getElementById(“company_url”);
state.company_url = hp ? hp.value : “”;
var payload = {};
Object.keys(state).forEach(function (k) { payload[k] = state[k]; });
payload.submitted_at = new Date().toISOString();
payload.source = “estimate_gateway_v4”;
payload.referrer = document.referrer || “”;
payload.user_agent = navigator.userAgent;
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) {
if (!res.ok && res.status >= 500) throw new Error(“Server error ” + res.status);
return res.text().then(function () { return { ok: true }; });
}).then(function () {
try { sessionStorage.removeItem(STORAGE_KEY); } catch (e) { /* ignore */ }
if (window.dataLayer) {
window.dataLayer.push({
event: “fcpe_estimate_submitted”,
services: payload.services,
bundles: payload.bundles,
plans: payload.plans,
timing: payload.timing,
cadence: payload.cadence
});
}
showSuccess(payload.first_name || “friend”);
}).catch(function (err) {
submitBtn.disabled = false;
setBtnLabel(submitBtn, “Request My Proposal”);
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” });
} // ———- INIT ———-
syncServices();
goToStep(hydratedStep || 1); try { window.__fcpeEstimateState = state; } catch (e) { /* ignore */ }
})();
Manage Consent
To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.
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.
function fcpePfSubscribe(btn){var input=document.getElementById('fcpe-pf-email');var email=(input?input.value:'').trim();if(!email||!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)){if(input){input.style.borderColor='#8B3A2A';setTimeout(function(){input.style.borderColor='rgba(212,170,55,.18)';},2000);}
return;}
btn.textContent='Subscribed â';btn.style.borderColor='#3C7251';btn.style.color='#3C7251';btn.disabled=true;if(input)input.value='';}
HOA-Ready Service. HOA-conscious scheduling and arrival protocols are standard practice in the communities we serve. We coordinate around community access requirements, contractor parking rules, and arrival windows before the first visit. Communicate your community’s specific requirements at booking and we will confirm them before we arrive.