Shop
window.MetrogreenChatbot = {“assistantName”: “Katherin”, “avatarSrc”: “https://metrogreenclean.com/wp-content/uploads/2026/06/katherin-metrogreen-chat-avatar.png”, “avatarAlt”: “Katherin from Metrogreen”};
(() => {
const defaults = {
brand: “Metrogreen”,
assistantName: “Katherin”,
phoneDisplay: “469-486-3390”,
phoneHref: “tel:+14694863390”,
bookingUrl: “https://metrogreencleaningcompany.bookingkoala.com/booknow”,
siteUrl: “https://metrogreenclean.com/”,
avatarSrc: “./katherin-chat.png”,
avatarAlt: “Katherin from Metrogreen”,
leadCapture: {
endpoint: “https://metrogreenclean.com/wp-json/sureforms/v1/submit-form”,
formId: “3080”,
submitToken: “e23acccc99bf5d217a1e5100babb1a49ac8db1149b8d0fbc39713e640b697d54”,
senderEmailField: “srfm-sender-email-field”,
fields: {
firstName: “srfm-input-2dadae53-lbl-Rmlyc3QgTmFtZQ-srfm-input”,
lastName: “srfm-input-23aceb7b-lbl-TGFzdCBOYW1l-last-name”,
email: “srfm-email-d7ccba8e-lbl-RW1haWw-srfm-email”,
phone: “srfm-phone-573b1287-lbl-UGhvbmUgTnVtYmVy-srfm-phone”,
smsOptIn:
“srfm-checkbox-e64b5787-lbl-QnkgY2hlY2tpbmcgdGhpcyBib3ggeW91IGFyZSBvcHRpbmctaW4gdG8gcmVjZWl2aW5nIHRleHQgbWVzc2FnZXM-by-checking-this-box-you-are-opting-in-to-receiving-text-messages”,
},
},
services: {
recurring: {
name: “Recurring Cleaning”,
url: “https://metrogreenclean.com/recurring-cleaning/”,
},
deep: {
name: “Deep Cleaning”,
url: “https://metrogreenclean.com/deep-cleaning/”,
},
move: {
name: “Move In-Move Out Cleaning”,
url: “https://metrogreenclean.com/movein-moveout/”,
},
commercial: {
name: “Light Commercial Cleaning”,
url: “https://metrogreenclean.com/light-commercial/”,
},
},
};
const runtimeConfig = window.MetrogreenChatbot || {};
const config = {
…defaults,
…runtimeConfig,
services: {
…defaults.services,
…(runtimeConfig.services || {}),
},
leadCapture: {
…defaults.leadCapture,
…(runtimeConfig.leadCapture || {}),
fields: {
…defaults.leadCapture.fields,
…((runtimeConfig.leadCapture && runtimeConfig.leadCapture.fields) || {}),
},
},
};
const styles = `
.mg-chatbot {
–mg-ink: #14213f;
–mg-ink-soft: #4b5b79;
–mg-ink-muted: #687896;
–mg-leaf: #57b72f;
–mg-leaf-deep: #459d28;
–mg-surface: #ffffff;
–mg-surface-soft: #f7fbf6;
–mg-line: rgba(20, 33, 63, 0.1);
–mg-line-strong: rgba(20, 33, 63, 0.18);
–mg-shadow: 0 24px 60px rgba(20, 33, 63, 0.18);
–mg-radius: 24px;
position: fixed;
right: 20px;
bottom: 20px;
z-index: 99999;
font-family: “Roboto”, Arial, sans-serif;
color: var(–mg-ink);
}
.mg-chatbot * { box-sizing: border-box; }
.mg-chatbot__panel {
width: min(408px, calc(100vw – 28px));
max-height: min(760px, calc(100vh – 110px));
display: none;
flex-direction: column;
background: var(–mg-surface);
border-radius: var(–mg-radius);
box-shadow: var(–mg-shadow);
border: 1px solid var(–mg-line);
overflow: hidden;
transform-origin: bottom right;
transform: scale(0.9);
opacity: 0;
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease;
}
.mg-chatbot.is-open .mg-chatbot__panel {
display: flex;
transform: scale(1);
opacity: 1;
}
.mg-chatbot__trigger {
position: absolute !important;
right: 0 !important;
bottom: 0 !important;
width: 68px !important;
height: 68px !important;
min-width: 68px !important;
min-height: 68px !important;
max-width: 68px !important;
max-height: 68px !important;
border-radius: 50% !important;
background: #fff !important;
border: 3px solid var(–mg-leaf) !important;
cursor: pointer !important;
box-shadow: 0 12px 28px rgba(87, 183, 47, 0.35) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
transition: transform 0.2s ease, box-shadow 0.2s ease !important;
z-index: 2 !important;
padding: 0 !important;
margin: 0 !important;
box-sizing: border-box !important;
flex-shrink: 0 !important;
overflow: hidden !important;
}
.mg-chatbot__trigger:hover { transform: translateY(-2px) !important; box-shadow: 0 16px 36px rgba(87, 183, 47, 0.45) !important; }
.mg-chatbot.is-open .mg-chatbot__trigger { display: none !important; }
.mg-chatbot__trigger img { width: 100% !important; height: 100% !important; object-fit: cover !important; display: block !important; }
.mg-chatbot__header { padding: 20px; background: linear-gradient(140deg, var(–mg-ink) 0%, var(–mg-ink-soft) 100%); color: #fff; display: flex; align-items: center; justify-content: space-between; }
.mg-chatbot__header-agent { display: flex; align-items: center; gap: 12px; }
.mg-chatbot__avatar { width: 44px !important; height: 44px !important; min-width: 44px !important; min-height: 44px !important; border-radius: 50% !important; object-fit: cover !important; border: 2px solid rgba(255, 255, 255, 0.2) !important; flex-shrink: 0 !important; }
.mg-chatbot__title { font-size: 16px !important; font-weight: 700 !important; font-family: “Jost”, Arial, sans-serif !important; margin: 0 !important; letter-spacing: 0.01em !important; }
.mg-chatbot__subtitle { font-size: 13px !important; opacity: 0.8 !important; margin: 2px 0 0 !important; }
.mg-chatbot__close { background: rgba(255, 255, 255, 0.1) !important; border: none !important; color: #fff !important; width: 32px !important; height: 32px !important; min-width: 32px !important; min-height: 32px !important; max-width: 32px !important; max-height: 32px !important; border-radius: 50% !important; display: flex !important; align-items: center !important; justify-content: center !important; cursor: pointer !important; transition: background 0.2s !important; padding: 0 !important; margin: 0 !important; flex-shrink: 0 !important; }
.mg-chatbot__close:hover { background: rgba(255, 255, 255, 0.2) !important; }
.mg-chatbot__close svg { display: block !important; flex-shrink: 0 !important; }
.mg-chatbot__body { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; background: var(–mg-surface-soft); }
.mg-message { display: flex; flex-direction: column; max-width: 85%; }
.mg-message–bot { align-self: flex-start; }
.mg-message–user { align-self: flex-end; align-items: flex-end; }
.mg-message__bubble { padding: 14px 18px; font-size: 15px; line-height: 1.5; border-radius: 18px; position: relative; }
.mg-message–bot .mg-message__bubble { background: #fff; color: var(–mg-ink); border: 1px solid var(–mg-line); border-bottom-left-radius: 4px; box-shadow: 0 4px 12px rgba(20, 33, 63, 0.04); }
.mg-message–user .mg-message__bubble { background: var(–mg-leaf); color: #fff; border-bottom-right-radius: 4px; }
.mg-chatbot__options { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.mg-chatbot__btn { padding: 12px 16px; background: #fff; border: 1px solid var(–mg-line-strong); border-radius: 12px; color: var(–mg-ink); font-size: 14px; font-weight: 500; cursor: pointer; text-align: left; transition: all 0.2s ease; box-shadow: 0 2px 8px rgba(20, 33, 63, 0.03); }
.mg-chatbot__btn:hover { border-color: var(–mg-leaf); color: var(–mg-leaf-deep); transform: translateY(-1px); }
.mg-chatbot__actions { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(–mg-line); }
.mg-action-btn { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 10px; border-radius: 10px; font-size: 14px; font-weight: 600; text-decoration: none; text-align: center; }
.mg-action-btn–primary { background: linear-gradient(135deg, var(–mg-leaf) 0%, var(–mg-leaf-deep) 100%); color: #fff; box-shadow: 0 8px 20px rgba(87, 183, 47, 0.25); }
.mg-action-btn–secondary { background: rgba(20, 33, 63, 0.05); color: var(–mg-ink); }
.mg-lead-form { display: flex; flex-direction: column; gap: 12px; margin-top: 12px; background: #fff; padding: 16px; border-radius: 16px; border: 1px solid var(–mg-line); }
.mg-lead-form input[type=”text”], .mg-lead-form input[type=”email”], .mg-lead-form input[type=”tel”] { width: 100%; padding: 12px 14px; border: 1px solid var(–mg-line-strong); border-radius: 8px; font-size: 14px; outline: none; transition: border-color 0.2s; }
.mg-lead-form input:focus { border-color: var(–mg-leaf); }
.mg-lead-form label.mg-checkbox { display: flex; gap: 8px; align-items: flex-start; font-size: 12px; color: var(–mg-ink-muted); cursor: pointer; line-height: 1.4; }
.mg-lead-form button { padding: 12px; background: var(–mg-ink); color: #fff; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; margin-top: 4px; }
.mg-lead-form button:hover { background: var(–mg-ink-soft); }
.mg-error { color: #d93025; font-size: 13px; margin-top: 4px; display: none; }
`;
const container = document.createElement(“div”);
container.className = “mg-chatbot”;
const styleEl = document.createElement(“style”);
styleEl.textContent = styles;
container.appendChild(styleEl);
const panel = document.createElement(“div”);
panel.className = “mg-chatbot__panel”;
panel.innerHTML = `
Chat with ${config.assistantName}
Typically replies instantly
`;
const trigger = document.createElement(“button”);
trigger.className = “mg-chatbot__trigger”;
trigger.setAttribute(“aria-label”, “Open chat”);
trigger.innerHTML = ``;
container.appendChild(panel);
container.appendChild(trigger);
document.body.appendChild(container);
const bodyEl = panel.querySelector(“#mg-chat-body”);
const closeBtn = panel.querySelector(“.mg-chatbot__close”);
const toggleChat = () => {
container.classList.toggle(“is-open”);
if (container.classList.contains(“is-open”)) {
bodyEl.scrollTop = bodyEl.scrollHeight;
}
};
trigger.addEventListener(“click”, toggleChat);
closeBtn.addEventListener(“click”, toggleChat);
const addMessage = (text, type = “bot”, additionalHtml = “”) => {
const msg = document.createElement(“div”);
msg.className = `mg-message mg-message–${type}`;
msg.innerHTML = `
`;
bodyEl.appendChild(msg);
bodyEl.scrollTop = bodyEl.scrollHeight;
};
const handleFlow = (flowType) => {
switch (flowType) {
case “quote”:
addMessage(“I’d like a free estimate”, “user”);
setTimeout(() => {
const formHtml = `
Great! We’d love to help. Please fill out this quick form, and we’ll text or call you back shortly with your estimate.
`;
addMessage(“”, “bot”, formHtml);
bindForm();
}, 500);
break;
case “services”:
addMessage(“What services do you offer?”, “user”);
setTimeout(() => {
const servicesHtml = `
We offer several cleaning packages to fit your needs:
`;
addMessage(“”, “bot”, servicesHtml);
}, 500);
break;
case “contact”:
addMessage(“I need to contact support”, “user”);
setTimeout(() => {
const contactHtml = `
You can reach us directly right now!
`;
addMessage(“”, “bot”, contactHtml);
}, 500);
break;
}
};
const bindForm = () => {
const form = panel.querySelector(“#mg-lead-form”);
const errorEl = panel.querySelector(“#mg-form-error”);
if (!form) return;
form.addEventListener(“submit”, async (e) => {
e.preventDefault();
const btn = form.querySelector(“button”);
btn.textContent = “Sending…”;
btn.disabled = true;
errorEl.style.display = “none”;
// Dynamically fetch fresh submit token and REST nonce from the contact page to avoid expiration
let submitToken = config.leadCapture.submitToken;
let nonce = “”;
try {
const tokenResponse = await fetch(‘/contact/’);
if (tokenResponse.ok) {
const htmlText = await tokenResponse.text();
// Parse data-submit-token
const parser = new DOMParser();
const doc = parser.parseFromString(htmlText, ‘text/html’);
const formEl = doc.querySelector(‘#srfm-form-3080’) || doc.querySelector(‘[form-id=”3080″]’);
if (formEl && formEl.getAttribute(‘data-submit-token’)) {
submitToken = formEl.getAttribute(‘data-submit-token’);
console.log(“Dynamically obtained fresh SureForms token:”, submitToken);
}
// Parse REST nonce
const nonceMatch = htmlText.match(/”nonce”\s*:\s*”([a-f0-9]+)”/i) || htmlText.match(/createNonceMiddleware\(\s*”([a-f0-9]+)”\)/i);
if (nonceMatch) {
nonce = nonceMatch[1];
console.log(“Dynamically obtained REST nonce:”, nonce);
}
}
} catch (tokenErr) {
console.warn(“Failed to fetch dynamic tokens, using fallback:”, tokenErr);
}
const formData = new FormData(form);
const payload = new FormData();
payload.append(“form-id”, config.leadCapture.formId); // SureForms expects ‘form-id’
payload.append(“srfm-sender-email-field”, “”);
const fMap = config.leadCapture.fields;
payload.append(fMap.firstName, formData.get(“firstName”));
payload.append(fMap.lastName, formData.get(“lastName”));
payload.append(fMap.email, formData.get(“email”));
payload.append(fMap.phone, formData.get(“phone”));
payload.append(fMap.smsOptIn, formData.get(“smsOptIn”) ? “Yes” : “”);
try {
const headers = {
“Accept”: “application/json”,
“X-WP-Submit-Token”: submitToken
};
if (nonce) {
headers[“X-WP-Nonce”] = nonce;
}
const response = await fetch(config.leadCapture.endpoint, {
method: “POST”,
body: payload,
headers: headers
});
if (response.ok) {
form.innerHTML = `
Thanks, ${formData.get(“firstName”)}!
We’ve received your details and will be in touch shortly.
`;
} else {
throw new Error(“Form submission failed”);
}
} catch (err) {
errorEl.style.display = “block”;
btn.textContent = “Get My Estimate”;
btn.disabled = false;
}
});
};
panel.addEventListener(“click”, (e) => {
if (e.target.matches(“.mg-chatbot__btn[data-flow]”)) {
handleFlow(e.target.getAttribute(“data-flow”));
}
});
})();
