exitrealtycw / index.html
John2121's picture
Update index.html
d3c696e verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EXIT Realty CW | Property Concierge</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<style>
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
font-size: 15px;
}
/* Brand utilities (since Tailwind CDN has no custom theme) */
.text-exit-blue {
color: #1e40af !important;
}
.bg-exit-blue {
background-color: #1e40af !important;
}
.border-exit-blue {
border-color: #1e40af !important;
}
.bg-exit-green {
background-color: #22c55e !important;
}
.exit-gradient {
background: linear-gradient(135deg, #1e40af 0%, #22c55e 100%);
}
.exit-shadow {
box-shadow: 0 8px 32px 0 rgba(30, 64, 175, 0.18), 0 1.5px 6px 0 rgba(34, 197, 94, 0.10);
}
.exit-glass {
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(8px);
}
.exit-logo {
width: 110px;
height: auto;
}
.exit-btn {
background: #1e40af;
color: #fff;
font-weight: 600;
border-radius: 0.5rem;
transition: background 0.2s;
font-size: 0.95rem;
padding: 0.5rem 1rem;
}
.exit-btn:hover {
background: #143075;
}
/* Typing indicator */
.typing-dots {
display: inline-flex;
gap: 4px;
align-items: center;
}
.typing-dots span {
width: 4px;
height: 4px;
border-radius: 9999px;
background: currentColor;
opacity: 0.6;
animation: blink 1s infinite ease-in-out;
}
.typing-dots span:nth-child(2) {
animation-delay: 0.2s;
}
.typing-dots span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes blink {
0%,
80%,
100% {
opacity: 0.2
}
40% {
opacity: 1
}
}
/* Mobile-first adjustments */
@media (max-width: 640px) {
.exit-logo {
width: 90px;
}
main {
padding-left: 0.25rem !important;
padding-right: 0.25rem !important;
}
.exit-glass {
padding: 0.75rem !important;
}
.exit-glass h1 {
font-size: 1.1rem !important;
margin-top: 1.5rem !important;
}
.exit-glass p {
font-size: 0.92rem !important;
}
#concierge-chat-box {
height: 12rem !important;
font-size: 0.92rem;
}
.exit-glass form {
flex-direction: column !important;
gap: 0.25rem !important;
}
.exit-btn {
width: 100%;
padding: 0.5rem 0 !important;
font-size: 0.92rem;
}
.exit-glass .absolute {
top: -1.5rem !important;
padding: 0.5rem !important;
}
.exit-glass .absolute i {
width: 1.3rem !important;
height: 1.3rem !important;
}
.exit-glass {
border-width: 2px !important;
}
.why-exit-flex {
flex-direction: column !important;
}
.why-exit-flex>div {
min-width: 0 !important;
}
#bottom-nav {
height: 48px !important;
}
#bottom-nav i {
width: 1.1rem !important;
height: 1.1rem !important;
}
#bottom-nav span {
font-size: 0.7rem !important;
}
}
</style>
</head>
<body class="exit-gradient min-h-screen flex flex-col justify-center items-center">
<header class="w-full flex justify-center py-3">
<img src="https://www.exitrealty.com/images/logos/EXIT_Realty_Logo.png" alt="EXIT Realty CW Logo"
class="exit-logo shadow-lg rounded-xl bg-white p-1" style="max-width:72px;">
</header>
<main class="w-full max-w-2xl mx-auto px-6">
<section
class="exit-glass exit-shadow rounded-3xl p-10 flex flex-col items-center border-4 border-white relative">
<div
class="absolute -top-12 left-1/2 -translate-x-1/2 bg-white border-4 border-exit-blue rounded-full p-5 shadow-lg">
<i data-feather="message-circle" class="w-10 h-10 text-exit-blue"></i>
</div>
<h1 class="text-xl font-extrabold text-exit-blue mt-2 mb-1 text-center tracking-tight"
style="line-height:1.1">Property Concierge</h1>
<p class="text-lg text-gray-700 mb-6 text-center max-w-lg">Experience the next level of real estate service.
Our AI-powered Property Concierge is here to answer your questions, match you with the perfect home, and
guide you through every step of your real estate journey—24/7, with a personal touch.</p>
<div id="concierge-chat-box"
class="w-full h-80 bg-gray-50 rounded-xl border border-gray-200 p-4 overflow-y-auto mb-4 flex flex-col space-y-2">
</div>
<!-- Profile: display name and email -->
<div id="profile-panel" class="w-full mb-3 grid grid-cols-1 sm:grid-cols-2 gap-2">
<input id="display-name-input" class="border rounded px-3 py-2" placeholder="Your name (for display)" />
<input id="display-email-input" class="border rounded px-3 py-2" placeholder="Your email (optional)" />
</div>
<div id="quick-replies" class="w-full flex flex-wrap gap-2 mb-3">
<button class="px-3 py-1.5 text-sm bg-white border rounded-full hover:bg-gray-50"
data-qr="I want to talk to an agent">Talk to an agent</button>
<button class="px-3 py-1.5 text-sm bg-white border rounded-full hover:bg-gray-50"
data-qr="I'd like to schedule a showing">Schedule a showing</button>
<button class="px-3 py-1.5 text-sm bg-white border rounded-full hover:bg-gray-50"
data-qr="Please provide a free valuation of my home">Get a valuation</button>
<button class="px-3 py-1.5 text-sm bg-white border rounded-full hover:bg-gray-50"
data-qr="Show me new listings today in Plover, WI">New listings today</button>
</div>
<form id="concierge-chat-form" class="w-full flex gap-2 sticky bottom-16 bg-white z-40 pt-2 pb-2"
autocomplete="off" style="box-shadow:0 -2px 8px 0 rgba(30,64,175,0.04)">
<input id="concierge-chat-input" type="text" placeholder="Type your message..."
class="flex-1 px-4 py-2 rounded-lg border border-gray-300 focus:border-exit-blue focus:ring-1 focus:ring-exit-blue focus:outline-none"
required>
<button type="submit" class="exit-btn px-6 py-2 flex items-center gap-1">
<i data-feather="send" class="w-4 h-4"></i>
<span>Send</span>
</button>
</form>
<div id="lead-capture" class="w-full mt-3 hidden">
<div class="bg-white border rounded-xl p-3">
<div class="text-sm font-semibold text-exit-blue mb-2">Connect with the EXIT team</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-2">
<input id="lead-name" class="border rounded px-3 py-2" placeholder="Full name" />
<input id="lead-email" class="border rounded px-3 py-2" placeholder="Email" />
<input id="lead-phone" class="border rounded px-3 py-2" placeholder="Phone" />
</div>
<div class="flex items-center gap-2 mt-2">
<button id="lead-submit" class="exit-btn px-4 py-2">Send to EXIT team</button>
<a id="lead-call" href="tel:+17155983794" class="px-3 py-2 text-sm underline">Call (715)
598-3794</a>
<a id="lead-email-link" href="mailto:info@exitcw.com?subject=New Inquiry"
class="px-3 py-2 text-sm underline">Email info@exitcw.com</a>
</div>
<div id="lead-status" class="text-xs text-gray-500 mt-2"></div>
</div>
</div>
<div class="mt-4 text-xs text-gray-400 text-center">Your conversation is private and secure. EXIT Realty CW
is committed to your success.</div>
</section>
<section class="mt-12 text-center">
<h2 class="text-2xl font-bold text-white mb-2">Why Choose EXIT Realty CW?</h2>
<div class="flex why-exit-flex flex-col md:flex-row gap-6 justify-center">
<div class="bg-white/90 rounded-xl p-6 flex-1 min-w-[220px]">
<i data-feather="users" class="w-8 h-8 text-exit-blue mb-2"></i>
<div class="font-semibold text-exit-blue mb-1">Local Experts</div>
<div class="text-gray-600 text-sm">Our agents know the neighborhoods, schools, and market trends
inside out.</div>
</div>
<div class="bg-white/90 rounded-xl p-6 flex-1 min-w-[220px]">
<i data-feather="home" class="w-8 h-8 text-exit-blue mb-2"></i>
<div class="font-semibold text-exit-blue mb-1">Personalized Service</div>
<div class="text-gray-600 text-sm">We tailor every experience to your needs, whether buying,
selling, or investing.</div>
</div>
<div class="bg-white/90 rounded-xl p-6 flex-1 min-w-[220px]">
<i data-feather="award" class="w-8 h-8 text-exit-blue mb-2"></i>
<div class="font-semibold text-exit-blue mb-1">Trusted Results</div>
<div class="text-gray-600 text-sm">EXIT Realty CW delivers results you can count on, with integrity
and care.</div>
</div>
</div>
</section>
<!-- Brokerage Intelligence: Zillow Blitz Assistant -->
<section class="mt-10 bg-white/95 rounded-2xl p-5 border shadow exit-shadow">
<div class="flex items-center justify-between gap-3 flex-wrap">
<h3 class="text-xl font-extrabold text-exit-blue">Daily Property Intelligence</h3>
<div id="intel-toast" class="hidden text-sm text-white bg-exit-blue px-3 py-1 rounded">Report generated
</div>
</div>
<div class="mt-4 grid gap-4">
<!-- Source selection -->
<div class="grid gap-3">
<div class="text-sm font-semibold">Sources</div>
<div class="flex flex-wrap gap-3">
<label class="flex items-center gap-2 bg-gray-100 rounded-md px-3 py-2"><input id="src-zillow"
type="checkbox" checked> <span>Zillow</span></label>
<label class="flex items-center gap-2 bg-gray-100 rounded-md px-3 py-2"><input id="src-homes"
type="checkbox" checked> <span>Homes.com</span></label>
<label class="flex items-center gap-2 bg-gray-100 rounded-md px-3 py-2"><input id="src-mls"
type="checkbox"> <span>MLS (CSV)</span></label>
</div>
<div class="flex items-center gap-2 text-xs text-gray-600">
<input id="intel-mls-csv" type="file" accept=".csv">
<span>Upload MLS CSV (optional)</span>
</div>
<div class="flex items-center gap-3">
<input id="intel-location" class="border rounded px-3 py-2 w-full"
placeholder="Location (e.g., Plover, WI)" value="Plover, WI" />
<button id="intel-generate" class="exit-btn px-4 py-2">Generate</button>
<span id="intel-loading" class="hidden text-sm text-gray-500">Loading…</span>
</div>
</div>
<!-- Filters -->
<div class="grid sm:grid-cols-2 md:grid-cols-4 gap-3">
<input id="f-price-min" class="border rounded px-3 py-2" placeholder="Min $" />
<input id="f-price-max" class="border rounded px-3 py-2" placeholder="Max $" />
<input id="f-city" class="border rounded px-3 py-2" placeholder="City" />
<select id="f-type" class="border rounded px-3 py-2">
<option value="">Any Type</option>
<option value="residential">Residential</option>
<option value="condo">Condo</option>
<option value="townhome">Townhome</option>
<option value="land">Land</option>
</select>
</div>
<!-- Dashboard -->
<div id="intel-dashboard" class=""></div>
<!-- View toggles & exports -->
<div class="flex flex-wrap items-center justify-between gap-3">
<label class="flex items-center gap-2 text-sm"><input id="intel-toggle-raw" type="checkbox">
<span>Show raw table</span></label>
<div class="flex gap-2">
<button id="intel-export-csv" class="bg-gray-800 text-white px-3 py-2 rounded">Export
CSV</button>
<button id="intel-export-pdf"
class="bg-gray-800 text-white px-3 py-2 rounded">Print/PDF</button>
</div>
</div>
<!-- Report Output -->
<div id="intel-report-output" class="grid gap-3"></div>
<!-- Scheduling & notifications -->
<div class="bg-gray-50 rounded-xl p-4 border grid gap-3">
<div class="font-semibold">Automation & Scheduling</div>
<div class="grid sm:grid-cols-2 md:grid-cols-4 gap-3 items-center">
<label class="flex items-center gap-2 text-sm"><input id="sched-enable" type="checkbox">
<span>Enable daily report</span></label>
<div class="flex items-center gap-2"><span class="text-sm text-gray-600">Time</span><input
id="sched-time" type="time" value="08:00" class="border rounded px-2 py-1" /></div>
<input id="slack-webhook" class="border rounded px-2 py-2"
placeholder="Slack webhook URL (optional)" />
<input id="email-to" class="border rounded px-2 py-2" placeholder="Email to (optional)" />
</div>
<div class="text-xs text-gray-500">Email sending requires a backend; we prepare a mailto link when
the report is ready. <a id="mailto-link" href="#" class="underline">Open draft</a></div>
</div>
</div>
</section>
</main>
<!-- Bottom Navigation Bar (Material style, mobile only) -->
<nav id="bottom-nav"
class="fixed bottom-0 left-0 w-full z-50 bg-white border-t border-gray-200 flex justify-around items-center h-16 shadow-lg sm:hidden">
<button id="nav-chat" class="flex flex-col items-center justify-center flex-1 h-full focus:outline-none"
aria-label="Chat">
<i data-feather="message-circle" class="w-6 h-6"></i>
<span class="text-xs mt-0.5">Chat</span>
</button>
<button id="nav-gmail" class="flex flex-col items-center justify-center flex-1 h-full focus:outline-none"
aria-label="Gmail Compose">
<i data-feather="mail" class="w-6 h-6"></i>
<span class="text-xs mt-0.5">Gmail</span>
</button>
<button id="nav-outlook" class="flex flex-col items-center justify-center flex-1 h-full focus:outline-none"
aria-label="Outlook Compose">
<i data-feather="send" class="w-6 h-6"></i>
<span class="text-xs mt-0.5">Outlook</span>
</button>
<a id="nav-call" href="tel:+17155983794"
class="flex flex-col items-center justify-center flex-1 h-full focus:outline-none" aria-label="Call">
<i data-feather="phone" class="w-6 h-6"></i>
<span class="text-xs mt-0.5">Call</span>
</a>
<a id="nav-email" href="mailto:info@exitcw.com?subject=New Inquiry"
class="flex flex-col items-center justify-center flex-1 h-full focus:outline-none" aria-label="Email">
<i data-feather="at-sign" class="w-6 h-6"></i>
<span class="text-xs mt-0.5">Email</span>
</a>
</nav>
<footer class="w-full text-center py-8 mt-12 text-white/80 text-sm">
&copy; 2025 EXIT Realty CW. All rights reserved. | <a href="#" class="underline hover:text-white">Privacy
Policy</a>
</footer>
<script src="chat.ddgs.js"></script>
<script src="brokerage-intel.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
feather.replace();
const chatBox = document.getElementById('concierge-chat-box');
const chatForm = document.getElementById('concierge-chat-form');
const chatInput = document.getElementById('concierge-chat-input');
const displayNameInput = document.getElementById('display-name-input');
const displayEmailInput = document.getElementById('display-email-input');
const navGmailBtn = document.getElementById('nav-gmail');
const navOutlookBtn = document.getElementById('nav-outlook');
const quickReplies = document.getElementById('quick-replies');
const leadCapture = document.getElementById('lead-capture');
const leadName = document.getElementById('lead-name');
const leadEmail = document.getElementById('lead-email');
const leadPhone = document.getElementById('lead-phone');
const leadSubmit = document.getElementById('lead-submit');
const leadStatus = document.getElementById('lead-status');
const userProfile = { name: '', email: '', phone: '', service: '', vehicle: '', mileage: '', details: '', contact_method: '', best_time: '', hear_about: '', urgency: '', newsletter: false };
const chatHistory = [];
const ASSISTANT_NAME = 'Ellie — EXIT Realty CW Concierge';
const empathyPhrases = [
"I hear you.",
"That makes total sense.",
"Thanks for sharing that.",
"I’ve got you.",
"We’ll make this easy."
];
const LLAMA_PROXY_URL = "https://llama-universal-netlify-project.netlify.app/.netlify/functions/llama-proxy?path=/chat/completions";
let exitTeamLinks = [];
chatInput.focus();
setTimeout(() => {
document.querySelector('main').scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 200);
// Prefetch EXIT team/contact links via DDGS
(async () => {
try {
const tool = window.propertyConciergeTools && window.propertyConciergeTools.ddgs;
if (!tool) return;
const q1 = await tool.fn({ query: 'EXIT Realty CW contact' });
const q2 = await tool.fn({ query: 'site:exitcw.com team' });
const q3 = await tool.fn({ query: 'site:exitcw.com agent' });
exitTeamLinks = [...(q1.results || []), ...(q2.results || []), ...(q3.results || [])]
.filter((r, i, arr) => r.url && arr.findIndex(x => x.url === r.url) === i)
.slice(0, 5);
} catch { }
})();
function safeLabel(name, fallback) {
const s = (name || '').trim();
return s.length ? s : fallback;
}
function addMessage(message, isUser = false) {
const msg = document.createElement('div');
msg.className = `flex ${isUser ? 'justify-end' : 'justify-start'}`;
const displayName = localStorage.getItem('display_name');
const userLabel = safeLabel(displayName, 'You');
const assistantLabel = 'Ellie';
const labelHtml = `<div class="text-[10px] text-gray-500 mb-0.5 ml-1">${isUser ? userLabel : assistantLabel}</div>`;
msg.innerHTML = `
<div>
${labelHtml}
<div class="inline-block max-w-xs px-4 py-2 rounded-lg shadow ${isUser ? 'bg-exit-blue text-white' : 'bg-gray-50 text-gray-900'}">
${message}
</div>
</div>
`;
chatBox.appendChild(msg);
chatBox.scrollTop = chatBox.scrollHeight;
}
// Typing indicator helpers
let typingEl = null;
function showTyping() {
if (typingEl) return;
typingEl = document.createElement('div');
typingEl.className = 'flex justify-start';
typingEl.innerHTML = `
<div class="inline-block max-w-xs px-4 py-2 rounded-lg shadow bg-gray-50 text-gray-900">
<span class="typing-dots"><span></span><span></span><span></span></span>
</div>`;
chatBox.appendChild(typingEl);
chatBox.scrollTop = chatBox.scrollHeight;
}
function hideTyping() {
if (typingEl?.parentNode) typingEl.parentNode.removeChild(typingEl);
typingEl = null;
}
function startConversation() {
const welcome = `Hi, I’m <b>${ASSISTANT_NAME}</b>. How can I help today? Looking for a home, selling, or just exploring?`;
addMessage(welcome);
chatHistory.push({ role: "assistant", content: welcome });
}
function extractProfileInfo(message) {
if (!userProfile.email && /@/.test(message) && /\./.test(message)) {
userProfile.email = message.match(/([\w.-]+@[\w.-]+)/)?.[1] || userProfile.email;
}
if (!userProfile.phone && /\d{3}[\s.-]?\d{3}[\s.-]?\d{4}/.test(message)) {
userProfile.phone = message.match(/(\d{3}[\s.-]?\d{3}[\s.-]?\d{4})/)?.[1] || userProfile.phone;
}
if (!userProfile.name && /my name is|i am|this is/i.test(message)) {
const nameMatch = message.match(/(?:my name is|i am|this is)\s+([A-Za-z ]+)/i);
if (nameMatch) userProfile.name = nameMatch[1].trim();
}
// Price range extraction
const range = message.match(/\$?\s*([0-9]{2,3}[,0-9]{0,3})\s*(k|K)?\s*[-to]{1,3}\s*\$?\s*([0-9]{2,3}[,0-9]{0,3})\s*(k|K)?/);
if (range) {
const low = Number(range[1].replace(/,/g, '')) * (range[2] ? 1000 : 1);
const high = Number(range[3].replace(/,/g, '')) * (range[4] ? 1000 : 1);
userProfile.price_low = low; userProfile.price_high = high;
}
const under = message.match(/under\s+\$?\s*([0-9]{2,3}[,0-9]{0,3})\s*(k|K)?/i);
if (under) {
const high = Number(under[1].replace(/,/g, '')) * (under[2] ? 1000 : 1);
userProfile.price_high = high;
}
const cityMatch = message.match(/in\s+([A-Za-z\s]+),\s*(WI|Wisconsin)/i);
if (cityMatch) { userProfile.city = cityMatch[1].trim(); userProfile.state = 'WI'; }
if (!userProfile.service && /(buy|sell|explor|listing|property|home|house|land|commercial)/i.test(message)) {
userProfile.service = message;
}
}
function extractAddress(message) {
const addressRegex = /(\d+\s+[\w\s]+,?\s*[\w\s]+,?\s*WI\b)/i;
const match = message.match(addressRegex);
return match ? match[0] : null;
}
function empathyPrefix(text) {
const casual = /^(hi|hello|hey|yo|sup|what'?s? up|howdy|good (morning|afternoon|evening)|greetings)[!.,\s]*$/i;
if (casual.test(text.trim())) return "";
if (/frustrat|stress|overwhelm|confus|worri|anxious|nerv|discourag|tough|hard|difficult|lost|stuck|scared|nervous|afraid|uncertain|unsure/i.test(text)) return empathyPhrases[0];
if (/thank|appreciate|grateful/i.test(text)) return "You're very welcome.";
if (/excited|great|awesome|amazing|yay|love/i.test(text)) return "Love the energy!";
return "";
}
async function llamaReply(lastUserMessage) {
const messages = chatHistory.map(m => ({ role: m.role, content: m.content }));
const systemPrompt =
`You are ${ASSISTANT_NAME}, a warm, friendly, and expert real estate concierge for EXIT Realty CW.\n\n` +
`Principles:\n` +
`- Match the user's tone: casual for greetings, empathetic only if the user expresses stress or negative emotion.\n` +
`- Be concise, human, and conversational (no scripts).\n` +
`- Ask one clear follow-up question at a time.\n` +
`- Offer concrete next steps: schedule a showing, request a valuation, connect with an agent.\n` +
`- When the user seems ready or asks about agents, proactively offer to connect with the EXIT team and collect name, email, and phone (one at a time).\n` +
`- Reflect user details (location, price range, timeline) to show you’re listening.\n` +
`- If the user mentions a specific address or property, suggest relevant steps (book a tour, check availability).\n` +
`- Keep it friendly, supportive, and encouraging.\n\n` +
`User profile so far: ${JSON.stringify(userProfile)}.`;
messages.unshift({ role: "system", content: systemPrompt });
const body = {
model: "Llama-3.3-8B-Instruct",
messages: messages,
max_tokens: 256,
temperature: 0.7
};
try {
showTyping();
const res = await fetch(LLAMA_PROXY_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
if (!res.ok) throw new Error("Llama API error");
const data = await res.json();
let reply = "(No response)";
if (data.choices?.[0]?.message?.content) {
reply = data.choices[0].message.content;
} else if (data.completion_message?.content?.text) {
reply = data.completion_message.content.text;
}
const prefix = empathyPrefix(lastUserMessage || '');
addMessage(`${prefix} ${reply}`);
chatHistory.push({ role: "assistant", content: reply });
extractProfileInfo(reply);
} catch (err) {
addMessage("Sorry, I'm having trouble reaching our concierge AI right now.");
} finally {
hideTyping();
}
}
async function agentReply(message) {
if (message.toLowerCase().startsWith('search:')) {
const query = message.replace(/^search:/i, '').trim();
addMessage('<span class="italic text-gray-500">Searching the web for: ' + query + ' ...</span>');
const tool = window.propertyConciergeTools && window.propertyConciergeTools.ddgs;
if (tool) {
const result = await tool.fn({ query });
if (result.results && result.results.length > 0) {
let html = '<b>Top web results:</b><ul class="list-disc pl-5">';
for (const r of result.results.slice(0, 3)) {
html += `<li><a href="${r.url}" target="_blank" class="text-exit-blue underline">${r.title}</a><br><span class="text-xs text-gray-600">${r.snippet}</span></li>`;
}
html += '</ul>';
addMessage(html);
} else {
addMessage('<span class="italic text-gray-500">No relevant web results found.</span>');
}
} else {
addMessage('<span class="italic text-red-500">Web search tool not available.</span>');
}
return;
}
const address = extractAddress(message);
if (address) {
addMessage('<span class="italic text-gray-500">Looking up information for: ' + address + ' ...</span>');
const tool = window.propertyConciergeTools && window.propertyConciergeTools.ddgs;
if (tool) {
const result = await tool.fn({ query: address });
if (result.results && result.results.length > 0) {
let html = '<b>Top web results for this property:</b><ul class="list-disc pl-5">';
for (const r of result.results.slice(0, 3)) {
html += `<li><a href="${r.url}" target="_blank" class="text-exit-blue underline">${r.title}</a><br><span class="text-xs text-gray-600">${r.snippet}</span></li>`;
}
html += '</ul>';
addMessage(html);
} else {
addMessage('<span class="italic text-gray-500">No relevant web results found for this address.</span>');
}
} else {
addMessage('<span class="italic text-red-500">Web search tool not available.</span>');
}
}
extractProfileInfo(message);
const funnelIntent = /(agent|talk to an agent|talk to agent|contact|call me|reach me|schedule|showing|valuation|market analysis|consult|appointment)/i.test(message);
if (funnelIntent) {
leadCapture.classList.remove('hidden');
let linksHtml = '';
if (exitTeamLinks.length) {
linksHtml = '<ul class="list-disc pl-5 text-sm">' + exitTeamLinks.slice(0, 3).map(l => `<li><a href="${l.url}" target="_blank" class="text-exit-blue underline">${l.title}</a></li>`).join('') + '</ul>';
}
addMessage(`I can connect you with the EXIT team right away. ${linksHtml || ''}`);
}
await llamaReply(message);
}
async function sendMessage(e) {
e.preventDefault();
const message = chatInput.value.trim();
if (!message) return;
addMessage(message, true);
chatHistory.push({ role: "user", content: message });
chatInput.value = '';
await agentReply(message);
}
chatForm.addEventListener('submit', sendMessage);
chatInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
chatForm.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
}
});
// Quick replies
if (quickReplies) {
quickReplies.addEventListener('click', (e) => {
const btn = e.target.closest('button[data-qr]');
if (!btn) return;
chatInput.value = btn.getAttribute('data-qr');
chatForm.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
});
}
// Profile inputs: load/save
try {
const savedName = localStorage.getItem('display_name');
const savedEmail = localStorage.getItem('display_email');
if (savedName) displayNameInput.value = savedName;
if (savedEmail) displayEmailInput.value = savedEmail;
if (savedEmail) userProfile.email = savedEmail;
if (savedName) userProfile.name = savedName;
} catch { }
displayNameInput.addEventListener('input', (e) => {
const v = e.target.value.trim();
try { localStorage.setItem('display_name', v); } catch { }
if (v) userProfile.name = v;
});
displayEmailInput.addEventListener('input', (e) => {
const v = e.target.value.trim();
try { localStorage.setItem('display_email', v); } catch { }
if (v) userProfile.email = v;
});
// Email compose helpers (Gmail/Outlook) with recent transcript
function stripHtml(s) { return (s || '').replace(/<[^>]*>/g, ''); }
function buildTranscript(limit = 12) {
const recent = chatHistory.slice(-limit);
const displayName = localStorage.getItem('display_name') || 'You';
return recent.map(m => {
const who = m.role === 'assistant' ? 'Ellie' : displayName;
return `[${who}] ${stripHtml(m.content)}`;
}).join('\n');
}
function openGmailCompose(to, subject, body) {
const url = `https://mail.google.com/mail/?view=cm&fs=1&to=${encodeURIComponent(to)}&su=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
window.open(url, '_blank');
}
function openOutlookCompose(to, subject, body) {
const url = `https://outlook.office.com/mail/deeplink/compose?to=${encodeURIComponent(to)}&subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
window.open(url, '_blank');
}
if (navGmailBtn) navGmailBtn.addEventListener('click', () => {
const to = 'info@exitcw.com';
const subject = 'EXIT CW | Chat with Ellie (handoff)';
const body = `Name: ${localStorage.getItem('display_name') || userProfile.name || ''}\nEmail: ${localStorage.getItem('display_email') || userProfile.email || ''}\n\nRecent transcript:\n${buildTranscript(12)}`;
openGmailCompose(to, subject, body);
});
if (navOutlookBtn) navOutlookBtn.addEventListener('click', () => {
const to = 'info@exitcw.com';
const subject = 'EXIT CW | Chat with Ellie (handoff)';
const body = `Name: ${localStorage.getItem('display_name') || userProfile.name || ''}\nEmail: ${localStorage.getItem('display_email') || userProfile.email || ''}\n\nRecent transcript:\n${buildTranscript(12)}`;
openOutlookCompose(to, subject, body);
});
// Scroll-aware hide/reveal for bottom nav and input
let lastScrollY = window.scrollY;
let navEl = document.getElementById('bottom-nav');
let chatFormEl = document.getElementById('concierge-chat-form');
window.addEventListener('scroll', () => {
const curr = window.scrollY;
if (curr > lastScrollY + 10) {
// scrolling down
navEl && (navEl.style.transform = 'translateY(100%)');
chatFormEl && (chatFormEl.style.transform = 'translateY(100%)');
} else if (curr < lastScrollY - 10) {
// scrolling up
navEl && (navEl.style.transform = 'translateY(0)');
chatFormEl && (chatFormEl.style.transform = 'translateY(0)');
}
lastScrollY = curr;
});
// On desktop, hide bottom nav and make input non-sticky
function handleResize() {
const navEl = document.getElementById('bottom-nav');
const chatFormEl = document.getElementById('concierge-chat-form');
if (window.innerWidth >= 640) {
navEl && (navEl.style.display = 'none');
chatFormEl && (chatFormEl.classList.remove('sticky', 'bottom-16', 'bg-white', 'z-40', 'pt-2', 'pb-2'));
} else {
navEl && (navEl.style.display = 'flex');
chatFormEl && (chatFormEl.classList.add('sticky', 'bottom-16', 'bg-white', 'z-40', 'pt-2', 'pb-2'));
}
}
window.addEventListener('resize', handleResize);
handleResize();
// Lead submit
if (leadSubmit) {
leadSubmit.addEventListener('click', async (e) => {
e.preventDefault();
const name = (leadName?.value || userProfile.name || '').trim();
const email = (leadEmail?.value || userProfile.email || '').trim();
const phone = (leadPhone?.value || userProfile.phone || '').trim();
if (!name || !email) { if (leadStatus) leadStatus.textContent = 'Please provide at least name and email.'; return; }
if (leadStatus) leadStatus.textContent = 'Sending…';
try {
const fd = new URLSearchParams();
fd.append('name', name);
fd.append('email', email);
fd.append('phone', phone);
fd.append('_subject', 'New Lead from Property Concierge (index2)');
fd.append('source', 'index2.html');
fd.append('timestamp', new Date().toISOString());
await fetch('https://formsubmit.co/ajax/info@exitcw.com', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: fd.toString() });
if (leadStatus) leadStatus.textContent = 'Thanks! Our team will reach out shortly.';
addMessage('Thanks! I’ve shared your details with our team. We’ll be in touch soon.');
leadCapture?.classList.add('hidden');
} catch (err) {
if (leadStatus) leadStatus.textContent = 'Could not send right now. Please call or email instead.';
}
});
}
setTimeout(() => {
startConversation();
}, 400);
});
</script>
</body>
</html>