{"roots":["0:3"],"nodeById":{"0:3":{"type":"WEBPAGE","id":"0:3","name":"/","absoluteBoundingBox":{"x":0.0,"y":0.0,"width":1408.0,"height":1244.0},"isolatedAbsoluteRenderBounds":{"x":0.0,"y":0.0,"width":1408.0,"height":1244.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":1408.0,"y":1244.0},"fills":[{"opacity":0.0470588244497776,"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true}],"strokeAlign":"INSIDE","strokes":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"children":["0:4"]},"0:4":{"type":"FRAME","id":"0:4","name":"Desktop","absoluteBoundingBox":{"x":64.0,"y":100.0,"width":1280.0,"height":1080.0},"isolatedAbsoluteRenderBounds":{"x":64.0,"y":100.0,"width":1280.0,"height":1080.0},"relativeTransform":[[1.0,0.0,64.0],[0.0,1.0,100.0]],"size":{"x":1280.0,"y":1080.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"clipsContent":true,"overflowDirection":"VERTICAL_SCROLLING","layoutMode":"VERTICAL","counterAxisSizingMode":"FIXED","isBreakpointFrame":true,"children":["0:7"]},"0:7":{"type":"CODE_INSTANCE","id":"0:7","name":"App","absoluteBoundingBox":{"x":64.0,"y":100.0,"width":1280.0,"height":1080.0},"isolatedAbsoluteRenderBounds":{"x":64.0,"y":100.0,"width":1280.0,"height":1080.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":1280.0,"y":1080.0},"fills":[],"strokeAlign":"INSIDE","layoutAlign":"STRETCH","layoutGrow":1.0,"strokes":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"codeExportName":"Code0_8.default","componentProperties":{"text#0:0":{"type":"TEXT","value":{"characters":"Code layer","characterStyleOverrides":[],"styleOverrideTable":{},"lineTypes":["NONE"],"lineIndentations":[0],"listStartOffsets":[],"lineStyleOverrides":[0]},"isDefault":true}}}},"assetIdToGuid":{},"guidToUrl":{"0:3":"/"},"fonts":{},"assets":{"c3410728d8f42f606dbb199dce2fa1767b02b47f":{"type":"GENERATED_ASSET","url":"c3410728d8f42f606dbb199dce2fa1767b02b47f.png","size":{"x":48.0,"y":48.0},"offsets":{"top":{"value":0,"unit":"PIXELS"},"right":{"value":0,"unit":"PIXELS"},"bottom":{"value":0,"unit":"PIXELS"},"left":{"value":0,"unit":"PIXELS"}},"format":"PNG"}},"stablePathToAssetInfo":{"13:129":{"hash":"c3410728d8f42f606dbb199dce2fa1767b02b47f"}},"animateRootIds":[],"siteSettings":{"title":"Client Foundry Local Businesses","description":"Delivers tailored marketing solutions for local businesses across Canada with SEO, website design, and lead generation to boost local business growth and online visibility.","scalingMode":"REFLOW","lang":"en","customCodeBodyEnd":"<!-- ============================================\n     CLIENT FOUNDRY — AI Chat Widget\n     Paste this into Webflow: Pages > Custom Code > Before </body>\n     \n     SETUP: Replace YOUR_LOGO_URL below with your actual logo URL\n     (upload your logo to Webflow Assets and paste the link here)\n     ============================================ -->\n\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css\">\n\n<style>\n  @import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:wght@400;500&display=swap');\n\n  .cf-wrap {\n    font-family: 'DM Sans', sans-serif;\n    position: fixed;\n    bottom: 24px;\n    right: 24px;\n    z-index: 99999;\n    width: 380px;\n  }\n\n  .cf-trigger {\n    display: flex;\n    align-items: center;\n    gap: 12px;\n    background: #F5C400;\n    color: #000;\n    border: none;\n    border-radius: 60px;\n    padding: 14px 22px;\n    cursor: pointer;\n    font-family: 'Syne', sans-serif;\n    font-size: 15px;\n    font-weight: 700;\n    letter-spacing: 0.01em;\n    transition: transform 0.15s, background 0.15s;\n    box-shadow: 0 4px 24px rgba(245,196,0,0.35);\n    width: 100%;\n    justify-content: center;\n  }\n  .cf-trigger:hover { background: #ffd000; transform: translateY(-2px); }\n\n  .pulse-dot {\n    width: 10px; height: 10px;\n    background: #000;\n    border-radius: 50%;\n    animation: cf-pulse 2s infinite;\n    flex-shrink: 0;\n  }\n  @keyframes cf-pulse {\n    0%,100% { box-shadow: 0 0 0 0 rgba(0,0,0,0.3); }\n    50% { box-shadow: 0 0 0 6px rgba(0,0,0,0); }\n  }\n\n  .cf-box {\n    background: #0a0a0a;\n    border-radius: 20px;\n    overflow: hidden;\n    display: none;\n    flex-direction: column;\n    height: 500px;\n    border: 1px solid #222;\n    margin-bottom: 12px;\n    box-shadow: 0 8px 48px rgba(0,0,0,0.5);\n  }\n  .cf-box.open { display: flex; }\n\n  .cf-header {\n    padding: 16px 20px 14px;\n    border-bottom: 1px solid #1a1a1a;\n    display: flex;\n    align-items: center;\n    gap: 12px;\n    flex-shrink: 0;\n    background: #111;\n  }\n\n  .cf-logo {\n    width: 40px; height: 40px;\n    border-radius: 10px;\n    object-fit: contain;\n    flex-shrink: 0;\n    background: #000;\n  }\n\n  .cf-header-info { flex: 1; }\n  .cf-header-name {\n    font-family: 'Syne', sans-serif;\n    font-weight: 800;\n    font-size: 14px;\n    color: #fff;\n    line-height: 1;\n    margin-bottom: 3px;\n  }\n  .cf-header-name span { color: #F5C400; }\n  .cf-header-status {\n    font-size: 12px;\n    color: #aaa;\n    display: flex;\n    align-items: center;\n    gap: 5px;\n  }\n  .cf-status-dot { width: 6px; height: 6px; background: #00e676; border-radius: 50%; }\n\n  .cf-close-btn {\n    background: #1a1a1a;\n    border: none;\n    color: #888;\n    width: 30px; height: 30px;\n    border-radius: 50%;\n    cursor: pointer;\n    font-size: 16px;\n    display: flex; align-items: center; justify-content: center;\n    transition: background 0.15s, color 0.15s;\n    flex-shrink: 0;\n  }\n  .cf-close-btn:hover { background: #2a2a2a; color: #fff; }\n\n  .cf-messages {\n    flex: 1;\n    overflow-y: auto;\n    padding: 16px;\n    display: flex;\n    flex-direction: column;\n    gap: 10px;\n    scrollbar-width: thin;\n    scrollbar-color: #222 transparent;\n  }\n\n  .cf-msg {\n    max-width: 82%;\n    padding: 10px 14px;\n    border-radius: 16px;\n    font-size: 13.5px;\n    line-height: 1.55;\n    animation: cf-fadeUp 0.2s ease;\n  }\n  @keyframes cf-fadeUp {\n    from { opacity: 0; transform: translateY(6px); }\n    to   { opacity: 1; transform: translateY(0); }\n  }\n\n  .cf-msg.bot {\n    background: #1a1a1a;\n    color: #e8e8e8;\n    border-bottom-left-radius: 4px;\n    align-self: flex-start;\n    border: 1px solid #252525;\n  }\n\n  .cf-msg.user {\n    background: #F5C400;\n    color: #000;\n    border-bottom-right-radius: 4px;\n    align-self: flex-end;\n    font-weight: 500;\n  }\n\n  .cf-typing {\n    display: none;\n    align-self: flex-start;\n    background: #1a1a1a;\n    border: 1px solid #252525;\n    border-radius: 16px;\n    border-bottom-left-radius: 4px;\n    padding: 12px 16px;\n    margin: 0 16px;\n  }\n  .cf-typing.show { display: flex; gap: 5px; align-items: center; }\n  .cf-typing span {\n    width: 6px; height: 6px;\n    background: #555;\n    border-radius: 50%;\n    animation: cf-bounce 1.2s infinite;\n  }\n  .cf-typing span:nth-child(2) { animation-delay: 0.2s; }\n  .cf-typing span:nth-child(3) { animation-delay: 0.4s; }\n  @keyframes cf-bounce {\n    0%,60%,100% { transform: translateY(0); }\n    30% { transform: translateY(-5px); background: #F5C400; }\n  }\n\n  .cf-quick-btns {\n    display: flex;\n    gap: 7px;\n    padding: 10px 16px 6px;\n    flex-wrap: wrap;\n    flex-shrink: 0;\n  }\n  .cf-qbtn {\n    background: transparent;\n    border: 1px solid #2a2a2a;\n    color: #aaa;\n    font-size: 12px;\n    padding: 5px 11px;\n    border-radius: 20px;\n    cursor: pointer;\n    font-family: 'DM Sans', sans-serif;\n    transition: all 0.15s;\n    white-space: nowrap;\n  }\n  .cf-qbtn:hover { border-color: #F5C400; color: #F5C400; background: rgba(245,196,0,0.06); }\n\n  .cf-input-row {\n    display: flex;\n    gap: 8px;\n    padding: 12px 16px 16px;\n    flex-shrink: 0;\n    border-top: 1px solid #1a1a1a;\n  }\n\n  .cf-input {\n    flex: 1;\n    background: #141414;\n    border: 1px solid #2a2a2a;\n    border-radius: 24px;\n    padding: 10px 16px;\n    color: #fff;\n    font-family: 'DM Sans', sans-serif;\n    font-size: 13.5px;\n    outline: none;\n    transition: border-color 0.15s;\n  }\n  .cf-input::placeholder { color: #444; }\n  .cf-input:focus { border-color: #F5C400; }\n\n  .cf-send-btn {\n    background: #F5C400;\n    border: none;\n    border-radius: 50%;\n    width: 40px; height: 40px;\n    display: flex; align-items: center; justify-content: center;\n    cursor: pointer;\n    flex-shrink: 0;\n    transition: transform 0.15s, background 0.15s;\n    color: #000;\n    font-size: 17px;\n  }\n  .cf-send-btn:hover { transform: scale(1.08); background: #ffd000; }\n  .cf-send-btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }\n\n  .cf-mic-btn {\n    background: #1a1a1a;\n    border: 1px solid #2a2a2a;\n    border-radius: 50%;\n    width: 40px; height: 40px;\n    display: flex; align-items: center; justify-content: center;\n    cursor: pointer;\n    flex-shrink: 0;\n    transition: all 0.15s;\n    color: #aaa;\n    font-size: 17px;\n  }\n  .cf-mic-btn:hover { border-color: #F5C400; color: #F5C400; }\n  .cf-mic-btn.listening {\n    background: rgba(245,196,0,0.15);\n    border-color: #F5C400;\n    color: #F5C400;\n    animation: cf-mic-pulse 1s infinite;\n  }\n  @keyframes cf-mic-pulse {\n    0%,100% { box-shadow: 0 0 0 0 rgba(245,196,0,0.4); }\n    50% { box-shadow: 0 0 0 6px rgba(245,196,0,0); }\n  }\n</style>\n\n<div class=\"cf-wrap\">\n  <div class=\"cf-box\" id=\"cfBox\">\n    <div class=\"cf-header\">\n      <!-- REPLACE: swap YOUR_LOGO_URL with your Webflow asset URL -->\n      <img class=\"cf-logo\" src=\"https://cdn.prod.website-files.com/69e7ea73a9f3f2eeda37d24a/69ed4b5e5185651bc5cfbf63_webclip_256x256.png\" alt=\"Client Foundry\" onerror=\"this.style.display='none'\" />\n      <div class=\"cf-header-info\">\n        <div class=\"cf-header-name\">CLIENT <span>FOUNDRY</span></div>\n        <div class=\"cf-header-status\"><span class=\"cf-status-dot\"></span>Online — replies instantly</div>\n      </div>\n      <button class=\"cf-close-btn\" onclick=\"cfToggle()\" aria-label=\"Close chat\"><i class=\"ti ti-x\"></i></button>\n    </div>\n\n    <div class=\"cf-messages\" id=\"cfMessages\">\n      <div class=\"cf-msg bot\">Hey 👋 I'm the Client Foundry AI. We help HVAC and home service businesses get more jobs from Google. What can I help you with?</div>\n    </div>\n\n    <div class=\"cf-typing\" id=\"cfTyping\"><span></span><span></span><span></span></div>\n\n    <div class=\"cf-quick-btns\" id=\"cfQuickBtns\">\n      <button class=\"cf-qbtn\" onclick=\"cfSendQuick('What services do you offer?')\">Services?</button>\n      <button class=\"cf-qbtn\" onclick=\"cfSendQuick('How much does a website cost?')\">Pricing?</button>\n      <button class=\"cf-qbtn\" onclick=\"cfSendQuick('Can I book a discovery call?')\">Book a call</button>\n    </div>\n\n    <div class=\"cf-input-row\">\n      <input class=\"cf-input\" id=\"cfInput\" placeholder=\"Ask me anything...\" onkeydown=\"if(event.key==='Enter')cfSend()\" />\n      <button class=\"cf-mic-btn\" id=\"cfMicBtn\" onclick=\"cfToggleMic()\" aria-label=\"Voice input\"><i class=\"ti ti-microphone\"></i></button>\n      <button class=\"cf-send-btn\" id=\"cfSendBtn\" onclick=\"cfSend()\" aria-label=\"Send\"><i class=\"ti ti-arrow-right\"></i></button>\n    </div>\n  </div>\n\n  <button class=\"cf-trigger\" onclick=\"cfToggle()\">\n    <span class=\"pulse-dot\"></span>\n    Chat with Client Foundry — We reply instantly\n  </button>\n</div>\n\n<script>\nconst CF_SYSTEM = `You are a friendly, sharp AI sales assistant for Client Foundry, a web design agency based in Ottawa that helps local service businesses (mainly HVAC, plumbing, and electrical contractors) get more jobs from Google.\n\nYour goals:\n1. Answer questions about Client Foundry's services clearly and confidently\n2. Build curiosity and interest in a free discovery call\n3. Collect the visitor's name and email or phone if they want to book a call\n4. Keep responses SHORT — 2-3 sentences max. This is a live chat widget.\n\nServices offered:\n- Custom high-converting websites (mobile-first, fast, built for local SEO)\n- Google Business Profile optimization\n- Local SEO (rank on Google Maps and search)\n- AI-powered chat agents for their own websites\n- Free website mockups and audits\n\nPricing: Never give specific prices. Always say something like 'It really depends on your needs and goals — that's exactly what we figure out on a quick discovery call.' Then push them toward booking the free call.\n\nDiscovery call CTA: Push visitors toward booking a free 20-minute discovery call. If they want to book, ask for their name and best contact (email or phone).\n\nTone: Confident, warm, a little punchy. No corporate fluff. Speak like a sharp founder, not a chatbot.`;\n\nlet cfHistory = [];\nlet cfIsOpen = false;\n\nfunction cfToggle() {\n  cfIsOpen = !cfIsOpen;\n  document.getElementById('cfBox').classList.toggle('open', cfIsOpen);\n  if (cfIsOpen) {\n    document.getElementById('cfInput').focus();\n    cfInitAudio();\n  }\n}\n\nfunction cfSendQuick(text) {\n  document.getElementById('cfInput').value = text;\n  cfSend();\n}\n\nasync function cfSend() {\n  const input = document.getElementById('cfInput');\n  const text = input.value.trim();\n  if (!text) return;\n\n  input.value = '';\n  document.getElementById('cfSendBtn').disabled = true;\n  document.getElementById('cfQuickBtns').style.display = 'none';\n\n  cfAppend(text, 'user');\n  cfHistory.push({ role: 'user', content: text });\n\n  const typing = document.getElementById('cfTyping');\n  typing.classList.add('show');\n  cfScroll();\n\n  try {\n    const res = await fetch('https://rapid-leaf-d650.shy-truth-eb02.workers.dev/', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        system: CF_SYSTEM,\n        messages: cfHistory\n      })\n    });\n    const data = await res.json();\n    const reply = data.content?.[0]?.text || \"Sorry, I had a hiccup — try again!\";\n    cfHistory.push({ role: 'assistant', content: reply });\n    typing.classList.remove('show');\n    cfAppend(reply, 'bot');\n    cfSpeak(reply);\n    cfCheckForLead(text, reply);\n  } catch(e) {\n    typing.classList.remove('show');\n    cfAppend(\"Something went wrong. Please try again!\", 'bot');\n  }\n\n  document.getElementById('cfSendBtn').disabled = false;\n  cfScroll();\n}\n\nfunction cfAppend(text, role) {\n  const el = document.createElement('div');\n  el.className = 'cf-msg ' + role;\n  el.textContent = text;\n  document.getElementById('cfMessages').appendChild(el);\n  cfScroll();\n}\n\nfunction cfScroll() {\n  const msgs = document.getElementById('cfMessages');\n  msgs.scrollTop = msgs.scrollHeight;\n}\n\n// Lead Detection & Formspree Submission\nlet cfLeadSent = false;\n\nfunction cfCheckForLead(userMsg, botReply) {\n  if (cfLeadSent) return;\n  const emailMatch = userMsg.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/);\n  const phoneMatch = userMsg.match(/(\\+?1?\\s?)?(\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4})/);\n  if (emailMatch || phoneMatch) {\n    cfLeadSent = true;\n    let name = 'Unknown';\n    for (const msg of cfHistory) {\n      if (msg.role === 'user') {\n        const nameMatch = msg.content.match(/^(my name is|i'm|i am|this is)\\s+([a-zA-Z]+)/i);\n        if (nameMatch) { name = nameMatch[2]; break; }\n      }\n    }\n    fetch('https://formspree.io/f/mojbozjy', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        name: name,\n        email: emailMatch ? emailMatch[0] : 'Not provided',\n        phone: phoneMatch ? phoneMatch[0] : 'Not provided',\n        source: 'AI Chat Widget',\n        message: 'Chat lead from website. User said: \"' + userMsg + '\"'\n      })\n    });\n  }\n}\n\n// ── Voice: Speech-to-Text (Web Speech API) ──\nlet cfRecognition = null;\nlet cfListening = false;\n\nfunction cfToggleMic() {\n  if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {\n    alert('Voice input is only supported in Chrome or Edge browsers.');\n    return;\n  }\n  if (cfListening) {\n    cfRecognition.stop();\n    return;\n  }\n  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;\n  cfRecognition = new SpeechRecognition();\n  cfRecognition.lang = 'en-US';\n  cfRecognition.interimResults = false;\n  cfRecognition.maxAlternatives = 1;\n\n  cfRecognition.onstart = () => {\n    cfListening = true;\n    const btn = document.getElementById('cfMicBtn');\n    btn.classList.add('listening');\n    btn.innerHTML = '<i class=\"ti ti-microphone-off\"></i>';\n    document.getElementById('cfInput').placeholder = 'Listening...';\n  };\n\n  cfRecognition.onresult = (e) => {\n    const transcript = e.results[0][0].transcript;\n    document.getElementById('cfInput').value = transcript;\n    cfSend();\n  };\n\n  cfRecognition.onerror = () => { cfStopMic(); };\n  cfRecognition.onend = () => { cfStopMic(); };\n  cfRecognition.start();\n}\n\nfunction cfStopMic() {\n  cfListening = false;\n  const btn = document.getElementById('cfMicBtn');\n  btn.classList.remove('listening');\n  btn.innerHTML = '<i class=\"ti ti-microphone\"></i>';\n  document.getElementById('cfInput').placeholder = 'Ask me anything...';\n}\n\n// ── Voice: Text-to-Speech (Browser built-in) ──\nlet cfAudioContext = null;\n\nfunction cfInitAudio() {\n  if (!cfAudioContext) {\n    cfAudioContext = new (window.AudioContext || window.webkitAudioContext)();\n  }\n  if (cfAudioContext.state === 'suspended') {\n    cfAudioContext.resume();\n  }\n}\n\nasync function cfSpeak(text) {\n  try {\n    const res = await fetch('https://rapid-leaf-d650.shy-truth-eb02.workers.dev/', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({ type: 'tts', text: text })\n    });\n    if (!res.ok) throw new Error('TTS failed');\n    const blob = await res.blob();\n    const url = URL.createObjectURL(new Blob([blob], { type: 'audio/mpeg' }));\n    const audio = new Audio(url);\n    audio.onended = () => URL.revokeObjectURL(url);\n    await audio.play();\n  } catch(e) {\n    console.log('ElevenLabs error:', e);\n    if (!window.speechSynthesis) return;\n    window.speechSynthesis.cancel();\n    const utter = new SpeechSynthesisUtterance(text);\n    utter.rate = 1.05;\n    window.speechSynthesis.speak(utter);\n  }\n}\n</script>","faviconFilename":"c3410728d8f42f606dbb199dce2fa1767b02b47f.png","socialImageFilename":"c3410728d8f42f606dbb199dce2fa1767b02b47f.png","labs":{"E5FBBA911B2B7A09E649D4BE6CDF8591EAEFC881":false}},"sourceCodeHash":"9f1a063c91b5493a4bd7b05fb333de3c90ce4e2d"}