* 4. Edit CONFIG.web3formsKey below with your real Web3Forms key (already public-safe by design).
*/
(function () {
const CONFIG = {
companyName: "Capital New York Home Advantage",
agentName: "Michael Albano — Capital New York Home Advantage LLC",
phone: "(518) 400-1188",
email: "malbano@capnyhome.com",
chatEndpoint: "/wp-json/capny/v1/chat", // handled by capny-chat-proxy.php
web3formsKey: "6d638681-a547-4d66-a0fa-1a1a0c05b2b9",
};
const COLORS = {
ink: "#14213D",
paper: "#F3EDE0",
gold: "#C9A24B",
green: "#2F5233",
text: "#241F1B",
muted: "#8A8375",
};
let messages = [
{
role: "assistant",
content:
"Hi, I'm Claire — I help get you started with " +
CONFIG.companyName +
". First things first: what's your name?",
},
];
let lead = null;
let loading = false;
let sendStatus = "idle";
let followupStatus = "idle";
let followupChannel = "";
let open = false;
// ---------- styles ----------
const style = document.createElement("style");
style.textContent = `
@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600&family=IBM+Plex+Mono:wght@400;500&family=Inter:wght@400;500;600&display=swap');
#capny-launcher { position: fixed; bottom: 24px; right: 24px; z-index: 999999; }
#capny-launcher button { width: 56px; height: 56px; border-radius: 999px; border: none; cursor: pointer;
background: ${COLORS.gold}; color: ${COLORS.ink}; box-shadow: 0 8px 24px rgba(0,0,0,.25);
display: flex; align-items: center; justify-content: center; font-size: 24px; }
#capny-panel { width: 360px; max-width: 92vw; height: 600px; max-height: 80vh; background: ${COLORS.paper};
border-radius: 4px; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,.35); display: flex; flex-direction: column;
font-family: 'Inter', sans-serif; position: fixed; bottom: 24px; right: 24px; z-index: 999999; }
#capny-panel.hidden, #capny-launcher.hidden { display: none; }
.capny-header { background: ${COLORS.ink}; padding: 14px 18px; display: flex; justify-content: space-between; align-items: center;
border-bottom: 3px double ${COLORS.gold}; }
.capny-eyebrow { font: 11px 'IBM Plex Mono', monospace; letter-spacing: .2em; text-transform: uppercase; color: ${COLORS.gold}; }
.capny-title { font: 18px 'Fraunces', serif; color: ${COLORS.paper}; }
.capny-close { background: none; border: none; color: ${COLORS.paper}; font-size: 18px; cursor: pointer; }
.capny-body { flex: 1; overflow-y: auto; padding: 16px;
background-image: repeating-linear-gradient(to bottom, transparent, transparent 43px, rgba(20,33,61,0.13) 44px); }
.capny-row { display: flex; gap: 8px; margin-bottom: 14px; }
.capny-row.user { justify-content: flex-end; }
.capny-avatar { flex-shrink: 0; width: 24px; height: 24px; border-radius: 999px; background: ${COLORS.ink}; color: ${COLORS.gold};
font: 10px 'IBM Plex Mono', monospace; display: flex; align-items: center; justify-content: center; margin-top: 2px; }
.capny-bubble { max-width: 78%; padding: 8px 12px; font-size: 14px; line-height: 1.35; border-radius: 3px; }
.capny-bubble.user { background: ${COLORS.ink}; color: ${COLORS.paper}; }
.capny-bubble.assistant { background: #fff; color: ${COLORS.text}; border: 1px solid rgba(20,33,61,.15); }
.capny-lead-card { position: relative; margin-top: 8px; padding: 14px; background: #fff; border: 1px solid rgba(20,33,61,.2); border-radius: 3px; }
.capny-stamp { position: absolute; top: -12px; right: -8px; transform: rotate(-8deg); padding: 4px 10px; font: 11px 'IBM Plex Mono', monospace;
text-transform: uppercase; letter-spacing: .1em; border: 2px solid ${COLORS.green}; color: ${COLORS.green}; background: rgba(47,82,51,.06); }
.capny-entry { font: 11px 'IBM Plex Mono', monospace; letter-spacing: .2em; text-transform: uppercase; color: ${COLORS.muted}; margin-bottom: 6px; }
.capny-dl { font-size: 13px; }
.capny-dl div { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px dashed rgba(20,33,61,.13); }
.capny-dl div:last-child { border-bottom: none; }
.capny-dl dt { color: ${COLORS.muted}; }
.capny-followup { margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(20,33,61,.1); font-size: 12px; color: ${COLORS.muted}; }
.capny-status { margin-top: 8px; font-size: 12px; }
.capny-status a { color: ${COLORS.ink}; }
.capny-input-row { display: flex; gap: 8px; padding: 10px; background: #EAE3D2; border-top: 1px solid rgba(20,33,61,.15); }
.capny-input-row input { flex: 1; padding: 8px 10px; font-size: 14px; border-radius: 3px; border: 1px solid rgba(20,33,61,.2); background: #fff; color: ${COLORS.text}; }
.capny-input-row button { width: 36px; height: 36px; border-radius: 3px; border: none; background: ${COLORS.ink}; color: ${COLORS.gold}; cursor: pointer; }
.capny-footer { background: ${COLORS.ink}; padding: 8px; display: flex; justify-content: center; gap: 16px;
font: 11px 'IBM Plex Mono', monospace; color: rgba(243,237,224,.7); }
`;
document.head.appendChild(style);
// ---------- DOM scaffold ----------
const launcher = document.createElement("div");
launcher.id = "capny-launcher";
launcher.innerHTML = `💬 `;
const panel = document.createElement("div");
panel.id = "capny-panel";
panel.className = "hidden";
panel.innerHTML = `
➤
`;
document.body.appendChild(launcher);
document.body.appendChild(panel);
const bodyEl = panel.querySelector("#capny-body");
const inputEl = panel.querySelector("#capny-input");
function esc(s) {
const d = document.createElement("div");
d.textContent = s == null ? "" : String(s);
return d.innerHTML;
}
function render() {
let html = "";
messages.forEach((m) => {
if (m.role === "assistant") {
html += ``;
} else {
html += ``;
}
});
if (loading) {
html += ``;
}
if (lead) {
html += `
★ Qualified Lead
Entry No. 001
Name ${esc(lead.name)}
Goal ${esc(lead.goal)}
Timeline ${esc(lead.timeline)}
Range ${esc(lead.priceRange)}
Contact ${esc(lead.contact)}
${esc(CONFIG.agentName)} will follow up shortly.
`;
}
bodyEl.innerHTML = html;
if (lead) {
const followupEl = bodyEl.querySelector("#capny-followup-status");
if (followupStatus === "sent") {
const channelLabel = followupChannel === "sms" ? "text" : "confirmation email";
followupEl.textContent = "✓ Instant " + channelLabel + " sent to " + lead.contact;
}
else if (followupStatus === "skipped") followupEl.textContent = "";
}
if (lead) {
const statusEl = bodyEl.querySelector("#capny-send-status");
if (sendStatus === "sending") statusEl.textContent = "Sending to " + CONFIG.email + "…";
else if (sendStatus === "sent") statusEl.textContent = "✓ Emailed to " + CONFIG.email;
else if (sendStatus === "failed") {
const body = "Name: " + lead.name + "\nGoal: " + lead.goal + "\nTimeline: " + lead.timeline +
"\nPrice Range: " + lead.priceRange + "\nContact: " + lead.contact;
const mailto = "mailto:" + CONFIG.email + "?subject=" + encodeURIComponent("New lead: " + lead.name) +
"&body=" + encodeURIComponent(body);
statusEl.innerHTML = `Click to send this lead by email `;
}
}
bodyEl.scrollTop = bodyEl.scrollHeight;
}
async function notifyLeadInstantly(leadData) {
try {
const res = await fetch("/wp-json/capny/v1/notify-lead", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: leadData.name,
goal: leadData.goal,
timeline: leadData.timeline,
contact: leadData.contact,
}),
});
const data = await res.json();
followupStatus = data.sent ? "sent" : "skipped";
followupChannel = data.channel || "";
} catch (e) {
followupStatus = "skipped";
}
render();
}
async function emailLead(leadData) {
if (!CONFIG.web3formsKey) {
sendStatus = "failed";
render();
return;
}
sendStatus = "sending";
render();
try {
const res = await fetch("https://api.web3forms.com/submit", {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify({
access_key: CONFIG.web3formsKey,
subject: "New lead: " + leadData.name + " (" + leadData.goal + ")",
from_name: CONFIG.companyName + " — Claire (Website Chat)",
Name: leadData.name,
Goal: leadData.goal,
Timeline: leadData.timeline,
"Price Range": leadData.priceRange,
Contact: leadData.contact,
}),
});
const data = await res.json();
sendStatus = data.success ? "sent" : "failed";
} catch (e) {
sendStatus = "failed";
}
render();
}
async function send() {
const text = inputEl.value.trim();
if (!text || loading) return;
messages.push({ role: "user", content: text });
inputEl.value = "";
loading = true;
render();
try {
const res = await fetch(CONFIG.chatEndpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages: messages.map((m) => ({ role: m.role, content: m.content })) }),
});
const data = await res.json();
let reply = (data.content || []).map((b) => (b.type === "text" ? b.text : "")).join("\n").trim();
const match = reply.match(/([\s\S]*?)<\/LEAD_CAPTURED>/);
if (match) {
try {
const parsed = JSON.parse(match[1]);
lead = parsed;
emailLead(parsed);
notifyLeadInstantly(parsed);
} catch (e) {}
reply = reply.replace(match[0], "").trim();
}
messages.push({ role: "assistant", content: reply || "Got it, thank you." });
} catch (e) {
messages.push({ role: "assistant", content: "Sorry — I hit a snag on my end. Could you try that again?" });
} finally {
loading = false;
render();
}
}
launcher.querySelector("button").addEventListener("click", () => {
open = true;
launcher.classList.add("hidden");
panel.classList.remove("hidden");
render();
});
panel.querySelector(".capny-close").addEventListener("click", () => {
open = false;
panel.classList.add("hidden");
launcher.classList.remove("hidden");
});
panel.querySelector("#capny-send").addEventListener("click", send);
inputEl.addEventListener("keydown", (e) => {
if (e.key === "Enter") send();
});
render();
})();