!function(d){ const FP_CSS='https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css',FP_JS='https://cdn.jsdelivr.net/npm/flatpickr',CONFETTI_JS='https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js'; const inj={css:h=>{const l=d.createElement('link');l.rel='stylesheet';l.href=h;d.head.appendChild(l)},js:(s,c)=>{const e=d.createElement('script');e.src=s;if(c)e.onload=c;d.head.appendChild(e)}}; inj.css(FP_CSS);inj.js(FP_JS,initVF); function initVF(){ inj.js(CONFETTI_JS); const vf=d.createElement('script');vf.src='https://cdn.voiceflow.com/widget-next/bundle.mjs';vf.type='text/javascript'; vf.onload=()=>{ let V; const MZ=(()=>{const BP=1024;let m,o,c=0;const en=()=>{if(innerWidth>BP)return;if(!m){m=d.querySelector('meta[name="viewport"]');if(!m){m=d.createElement('meta');m.name='viewport';d.head.appendChild(m)}o=m.getAttribute('content')||'width=device-width, initial-scale=1'}c++;m.setAttribute('content','width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover')},de=()=>{if(innerWidth>BP)return;setTimeout(()=>{c=Math.max(0,c-1);if(m&&c===0&&o!=null)m.setAttribute('content',o)},50)},bl=()=>{try{d.activeElement?.blur?.()}catch(e){}};return{enable:en,disable:de,blurActive:bl}})(); const CH=(()=>{const A='data-vf-hide-composer',CSS='.vfrc-input-container._1gdvh9t1{display:none!important}.vfrc-footer,.vfrc-composer,.vfrc-message-composer,.vfrc-chat-input-container{display:none!important}';let hid=false,roots=new Set,obs;const all=()=>{const res=new Set([d]),q=[d],seen=new Set(q);while(q.length){const r=q.shift(),nodes=(r instanceof Document||r instanceof ShadowRoot)?r.querySelectorAll('*'):[];nodes.forEach(el=>{if(el&&el.shadowRoot&&!seen.has(el.shadowRoot)){seen.add(el.shadowRoot);res.add(el.shadowRoot);q.push(el.shadowRoot)}})}return[...res]},add=r=>{if(!r||!(r instanceof Document||r instanceof ShadowRoot))return;if(r.querySelector(`style[${A}]`))return;const s=d.createElement('style');s.setAttribute(A,'1');s.textContent=CSS;try{r.appendChild(s);roots.add(r)}catch(e){}},rem=r=>{const s=r.querySelector?.(`style[${A}]`);if(s)try{s.remove()}catch(e){}roots.delete(r)};return{hide(){hid=true;all().forEach(add);if(!obs){obs=new MutationObserver(()=>{if(hid)all().forEach(add)});obs.observe(d.documentElement,{childList:true,subtree:true})}},show(){hid=false;roots.forEach(rem);roots.clear();obs?.disconnect?.();obs=null}}})(); const AH=(()=>{const SEL='.vfrc-avatar,[class*="vfrc-avatar"]',obsMap=new WeakMap(),tidy=c=>{c.querySelectorAll(SEL).forEach(a=>{a.style.display='none';a.style.visibility='hidden';a.style.width='0';a.style.margin='0';a.style.padding='0'})},find=n=>{let e=n;for(let i=0;i<12&&e;i++){if(e.querySelector?.(SEL))return e;e=e.parentElement||(e.getRootNode&&e.getRootNode().host)||null}return null},hideFor=node=>{const c=find(node);if(!c)return;tidy(c);if(obsMap.has(c))return;const mo=new MutationObserver(muts=>{let changed=false;for(const m of muts){if(m.addedNodes&&m.addedNodes.length)changed=true}if(changed)tidy(c)});mo.observe(c,{childList:true,subtree:true});obsMap.set(c,mo);setTimeout(()=>{mo.disconnect();obsMap.delete(c)},1200)};return{hideFor}})(); const SI=(()=>({inject:(css,attr)=>{const roots=[],q=[d],seen=new Set(q);while(q.length){const r=q.shift();roots.push(r);const nodes=(r instanceof Document||r instanceof ShadowRoot)?r.querySelectorAll('*'):[];nodes.forEach(el=>{if(el&&el.shadowRoot&&!seen.has(el.shadowRoot)){seen.add(el.shadowRoot);q.push(el.shadowRoot)}})}roots.forEach(r=>{if(!r||!(r instanceof Document||r instanceof ShadowRoot))return;if(r.querySelector(`style[${attr}]`))return;const s=d.createElement('style');s.setAttribute(attr,'1');s.textContent=css;try{r.appendChild(s)}catch(e){}})}}))(); /* ---- Global styles ---- */ SI.inject( '.vf-wide-card{width:100%!important;max-width:100%!important;display:block!important}' + '.vf-wide-card>.vf-form-card{width:100%!important;max-width:100%!important}' + '.vf-savefx-success{background:#00bf63!important;transition:background .22s ease,transform .22s ease,opacity .3s ease}' + '.vf-savefx-bounce{transform:translateY(-1px) scale(1.02)}' + '.vf-savefx-hide{opacity:0;transform:translateY(-6px) scale(.98);pointer-events:none;transition:opacity .28s ease,transform .28s ease}' + '.vf-progress-card{padding:16px;border:1px solid #e5e7eb;border-radius:8px;margin:12px 0;background:#fafafa;width:100%}' + '.vf-progress-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}' + '.vf-progress-title{font-weight:700;color:#111827;font-size:14px}' + '.vf-progress-sub{color:#6b7280;font-size:12px}' + '.vf-progress-track{position:relative;height:10px;background:#e5e7eb;border-radius:9999px;overflow:hidden}' + '.vf-progress-fill{position:absolute;inset:0 auto 0 0;width:0;height:100%;border-radius:9999px;background:linear-gradient(90deg,#4f46e5,#3b82f6,#06b6d4,#22c55e);transition:width .7s cubic-bezier(.22,1,.36,1)}' + '.vf-step-dots{display:flex;justify-content:space-between;margin-top:10px}' + '.vf-step-dot{width:22px;height:22px;border-radius:50%;background:#e5e7eb;display:flex;align-items:center;justify-content:center;color:#fff;font-size:12px;box-shadow:inset 0 0 0 2px #fff}' + '.vf-step-dot.complete{background:#22c55e}.vf-step-dot.current{background:#3b82f6;animation:vfPulse 1.2s ease-in-out infinite}' + '@keyframes vfPulse{0%{transform:scale(1);box-shadow:0 0 0 0 rgba(59,130,246,.55)}70%{box-shadow:0 0 0 10px rgba(59,130,246,0)}100%{transform:scale(1)}}' + '.vf-pros-card{padding:16px;border:1px solid #e5e7eb;border-radius:12px;margin:12px 0;background:#f8fafc;width:100%}' + '.vf-pros-hero{display:flex;gap:14px;align-items:center}' + '.vf-hero{position:relative;width:72px;height:72px;min-width:72px;min-height:72px;flex:0 0 auto;border-radius:50%;background:linear-gradient(135deg,#10b981,#22c55e);box-shadow:0 10px 26px rgba(16,185,129,.35)}' + '.vf-hero-ring{position:absolute;inset:-6px;border-radius:50%;border:2px solid rgba(16,185,129,.35);animation:vfRing 1.4s ease-out forwards}' + '@keyframes vfRing{0%{transform:scale(.7);opacity:.9}100%{transform:scale(1.35);opacity:0}}' + '.vf-check{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}' + '.vf-check path{stroke:#fff;stroke-width:6;fill:none;stroke-linecap:round;stroke-linejoin:round}' + '.vf-sparks{position:absolute;inset:0;pointer-events:none}' + '.vf-spark{position:absolute;color:#fcd34d;opacity:0;font-size:14px;transform:translate(0,0) scale(.7);animation:vfSpark 1.1s ease-out forwards}' + '@keyframes vfSpark{0%{opacity:0;transform:translate(0,0) scale(.7) rotate(0)}30%{opacity:1}100%{opacity:0;transform:translate(var(--tx,0),var(--ty,0)) scale(1) rotate(20deg)}}' + '.vf-pros-title{font-size:16px;font-weight:800;color:#0f172a}' + '.vf-pros-sub{font-size:14px;color:#334155;margin-top:2px}' + '.vf-pros-meta{font-size:12px;color:#64748b;margin-top:8px}' + '.vf-pros-stars{display:flex;gap:2px;margin-top:6px;color:#f59e0b}' + '@media(max-width:640px){.vf-pros-hero{flex-direction:column;align-items:stretch}.vf-hero{align-self:center;margin-bottom:8px}}', 'data-vf-styles' ); SI.inject('.vfrc-assistant-info .vfrc-avatar.g931q10.g931q13{display:block!important;visibility:visible!important;width:300px!important;height:300px!important;margin:initial!important;padding:initial!important}','data-vf-show-specific-avatar'); SI.inject( '.vf-loader-card{padding:16px;border:1px solid #e5e7eb;border-radius:12px;margin:12px 0;background:#f8fafc;width:100%}' + '.vf-loader-wrap{display:flex;align-items:center;gap:14px}' + '.vf-spinner{width:46px;height:46px;border-radius:9999px;border:4px solid #dbeafe;border-top-color:#0551fb;animation:vfSpin 1s linear infinite;flex:0 0 auto}' + '@keyframes vfSpin{to{transform:rotate(360deg)}}' + '.vf-loader-text{display:flex;flex-direction:column}' + '.vf-loader-title{font-weight:800;color:#0f172a;font-size:16px}' + '.vf-loader-sub{color:#334155;font-size:14px;margin-top:2px}' + '.vf-dots{display:inline-flex;gap:4px;margin-top:6px}' + '.vf-dot{width:6px;height:6px;border-radius:9999px;background:#0551fb;opacity:.35;animation:vfBounce 1s ease-in-out infinite}' + '.vf-dot:nth-child(2){animation-delay:.15s}.vf-dot:nth-child(3){animation-delay:.3s}' + '@keyframes vfBounce{0%,80%,100%{transform:translateY(0);opacity:.35}40%{transform:translateY(-6px);opacity:1}}', 'data-vf-loader-styles' ); /* ---- Geyser extension full-width overrides ---- */ SI.inject( '.vf-wide-card,' + '.vf-wide-card>.vf-form-card,' + '.vfrc-message.vfrc-message--extension-GeyserWelcome,' + '.vfrc-message.vfrc-message--extension-GeyserWelcome .vfrc-message__content,' + '.vfrc-message.vfrc-message--extension-GeyserResults,' + '.vfrc-message.vfrc-message--extension-GeyserResults .vfrc-message__content,' + '.vfrc-message.vfrc-message--extension-ext_geyserWelcome,' + '.vfrc-message.vfrc-message--extension-ext_geyserWelcome .vfrc-message__content,' + '.vfrc-message.vfrc-message--extension-ext_geyserResults,' + '.vfrc-message.vfrc-message--extension-ext_geyserResults .vfrc-message__content' + '{width:100%!important;max-width:100%!important;align-self:stretch!important;padding-left:0!important;padding-right:0!important;background:transparent!important;border:0!important;border-radius:0!important;box-shadow:none!important}', 'data-vf-fullwidth' ); SI.inject('.vfrc-launcher{display:none!important}','data-vf-hide-launcher'); function vfClosestWrapper(n){let e=n,h=0;while(e&&h<20){const c=(e.className||'')+'';if(e.getAttribute?.('role')==='listitem'||/vfrc-.*(message|bubble|response|chat|row)/i.test(c))return e;e=e.parentNode||(e.getRootNode&&e.getRootNode().host)||null;h++}return null} function removeIfEmptyWrapper(el){try{if(!el)return;if(el.childElementCount===0&&(el.textContent||'').trim()===''){const w=vfClosestWrapper(el)||el.parentNode;if(w)try{w.remove()}catch(e){}}}catch(e){}} function forceRemoveWrapper(el){try{if(!el)return;let current=el;for(let i=0;i<15;i++){if(!current)break;const classList=current.className||'';if(classList.includes('vfrc-system-response')){current.style.display='none';current.style.height='0';current.style.margin='0';current.style.padding='0';current.style.overflow='hidden';break}current=current.parentElement||(current.getRootNode&¤t.getRootNode().host)||null}}catch(e){}} const __VF_LOADERS=new Set(),__vfParsePayload=t=>{try{return typeof t?.payload==='string'?JSON.parse(t.payload):(t?.payload||{})}catch(_){return{}}}; function __vfRemoveAllLoaders(){const now=Date.now();__VF_LOADERS.forEach(x=>{const{node,contentEl,started,minMs}=x,delay=Math.max(0,(started+minMs)-now);setTimeout(()=>{try{node?.remove()}catch(e){}removeIfEmptyWrapper(contentEl);try{__VF_LOADERS.delete(x)}catch(e){}if(__VF_LOADERS.size===0){CH.show();MZ.disable()}},delay)})} /* ==================== TRACKING UTILITY ==================== */ const TRACK=(()=>{function trackEvent(name,params={}){if(typeof gtag!=='function')return;try{gtag('event',name,params)}catch(e){}}return{trackEvent}})(); /* ==================== TRACKING EXTENSIONS ==================== */ const ext_chatInitiated={name:'ext_chatInitiated',type:'response',match:({trace})=>trace?.type==='ext_chatInitiated'||trace?.payload?.name==='ext_chatInitiated',render:({element})=>{TRACK.trackEvent('ext_chatInitiated');forceRemoveWrapper(element);return()=>{}}}; const ext_conversationStarted={name:'ext_conversationStarted',type:'response',match:({trace})=>trace?.type==='ext_conversationStarted'||trace?.payload?.name==='ext_conversationStarted',render:({element})=>{TRACK.trackEvent('ext_conversationStarted');forceRemoveWrapper(element);return()=>{}}}; const ext_mainIntent={name:'ext_mainIntent',type:'response',match:({trace})=>trace?.type==='ext_mainIntent'||trace?.payload?.name==='ext_mainIntent',render:({trace,element})=>{const payload=__vfParsePayload(trace);TRACK.trackEvent('ext_mainIntent',{intent:payload.intent||'unknown'});forceRemoveWrapper(element);return()=>{}}}; const ext_askJess={name:'ext_askJess',type:'response',match:({trace})=>trace?.type==='ext_askJess'||trace?.payload?.name==='ext_askJess',render:({element})=>{TRACK.trackEvent('ext_askJess');forceRemoveWrapper(element);return()=>{}}}; const ext_supportBookPlumber={name:'ext_supportBookPlumber',type:'response',match:({trace})=>trace?.type==='ext_supportBookPlumber'||trace?.payload?.name==='ext_supportBookPlumber',render:({element})=>{TRACK.trackEvent('ext_supportBookPlumber');forceRemoveWrapper(element);return()=>{}}}; const ext_supportBookElectrician={name:'ext_supportBookElectrician',type:'response',match:({trace})=>trace?.type==='ext_supportBookElectrician'||trace?.payload?.name==='ext_supportBookElectrician',render:({element})=>{TRACK.trackEvent('ext_supportBookElectrician');forceRemoveWrapper(element);return()=>{}}}; const ext_electricianDiagnoseStart={name:'ext_electricianDiagnoseStart',type:'response',match:({trace})=>trace?.type==='ext_electricianDiagnoseStart'||trace?.payload?.name==='ext_electricianDiagnoseStart',render:({element})=>{TRACK.trackEvent('ext_electricianDiagnoseStart');forceRemoveWrapper(element);return()=>{}}}; const ext_plumbingDiagnoseStart={name:'ext_plumbingDiagnoseStart',type:'response',match:({trace})=>trace?.type==='ext_plumbingDiagnoseStart'||trace?.payload?.name==='ext_plumbingDiagnoseStart',render:({element})=>{TRACK.trackEvent('ext_plumbingDiagnoseStart');forceRemoveWrapper(element);return()=>{}}}; const ext_plumbingDiagnoseEnd={name:'ext_plumbingDiagnoseEnd',type:'response',match:({trace})=>trace?.type==='ext_plumbingDiagnoseEnd'||trace?.payload?.name==='ext_plumbingDiagnoseEnd',render:({trace,element})=>{const payload=__vfParsePayload(trace);TRACK.trackEvent('ext_plumbingDiagnoseEnd',{service_category:payload.service_category||'unknown',job_diagnosed:payload.job_diagnosed||'unknown'});forceRemoveWrapper(element);return()=>{}}}; const ext_electricianDiagnoseEnd={name:'ext_electricianDiagnoseEnd',type:'response',match:({trace})=>trace?.type==='ext_electricianDiagnoseEnd'||trace?.payload?.name==='ext_electricianDiagnoseEnd',render:({trace,element})=>{const payload=__vfParsePayload(trace);TRACK.trackEvent('ext_electricianDiagnoseEnd',{service_category:payload.service_category||'unknown',job_diagnosed:payload.job_diagnosed||'unknown'});forceRemoveWrapper(element);return()=>{}}}; const ext_serviceNotSupported={name:'ext_serviceNotSupported',type:'response',match:({trace})=>trace?.type==='ext_serviceNotSupported'||trace?.payload?.name==='ext_serviceNotSupported',render:({trace,element})=>{const payload=__vfParsePayload(trace);TRACK.trackEvent('ext_serviceNotSupported',{service_requested:payload.service_requested||'unknown'});forceRemoveWrapper(element);return()=>{}}}; const ext_proApplication={name:'ext_proApplication',type:'response',match:({trace})=>trace?.type==='ext_proApplication'||trace?.payload?.name==='ext_proApplication',render:({element})=>{TRACK.trackEvent('ext_proApplication');forceRemoveWrapper(element);return()=>{}}}; const ext_locationStart={name:'ext_locationStart',type:'response',match:({trace})=>trace?.type==='ext_locationStart'||trace?.payload?.name==='ext_locationStart',render:({element})=>{TRACK.trackEvent('ext_locationStart');forceRemoveWrapper(element);return()=>{}}}; const ext_locationFallback={name:'ext_locationFallback',type:'response',match:({trace})=>trace?.type==='ext_locationFallback'||trace?.payload?.name==='ext_locationFallback',render:({trace,element})=>{const payload=__vfParsePayload(trace);TRACK.trackEvent('ext_locationFallback',{location:payload.location||'unknown'});forceRemoveWrapper(element);return()=>{}}}; const ext_locationEnd={name:'ext_locationEnd',type:'response',match:({trace})=>trace?.type==='ext_locationEnd'||trace?.payload?.name==='ext_locationEnd',render:({trace,element})=>{const payload=__vfParsePayload(trace);TRACK.trackEvent('ext_locationEnd',{location:payload.location||'unknown'});forceRemoveWrapper(element);return()=>{}}}; const ext_noProsAvailable={name:'ext_noProsAvailable',type:'response',match:({trace})=>trace?.type==='ext_noProsAvailable'||trace?.payload?.name==='ext_noProsAvailable',render:({trace,element})=>{const payload=__vfParsePayload(trace);TRACK.trackEvent('ext_noProsAvailable',{location_requested:payload.location_requested||'unknown'});forceRemoveWrapper(element);return()=>{}}}; const ext_proCheckAPIFailed={name:'ext_proCheckAPIFailed',type:'response',match:({trace})=>trace?.type==='ext_proCheckAPIFailed'||trace?.payload?.name==='ext_proCheckAPIFailed',render:({element})=>{TRACK.trackEvent('ext_proCheckAPIFailed');forceRemoveWrapper(element);return()=>{}}}; const ext_checkForProsSuccess={name:'ext_checkForProsSuccess',type:'response',match:({trace})=>trace?.type==='ext_checkForProsSuccess'||trace?.payload?.name==='ext_checkForProsSuccess',render:({element})=>{TRACK.trackEvent('ext_checkForProsSuccess');forceRemoveWrapper(element);return()=>{}}}; const ext_dateTimeCaptureStart={name:'ext_dateTimeCaptureStart',type:'response',match:({trace})=>trace?.type==='ext_dateTimeCaptureStart'||trace?.payload?.name==='ext_dateTimeCaptureStart',render:({element})=>{TRACK.trackEvent('ext_dateTimeCaptureStart');forceRemoveWrapper(element);return()=>{}}}; const ext_dateUrgent={name:'ext_dateUrgent',type:'response',match:({trace})=>trace?.type==='ext_dateUrgent'||trace?.payload?.name==='ext_dateUrgent',render:({element})=>{TRACK.trackEvent('ext_dateUrgent');forceRemoveWrapper(element);return()=>{}}}; const ext_datePlanned={name:'ext_datePlanned',type:'response',match:({trace})=>trace?.type==='ext_datePlanned'||trace?.payload?.name==='ext_datePlanned',render:({element})=>{TRACK.trackEvent('ext_datePlanned');forceRemoveWrapper(element);return()=>{}}}; const ext_dateTimeCaptureEnd={name:'ext_dateTimeCaptureEnd',type:'response',match:({trace})=>trace?.type==='ext_dateTimeCaptureEnd'||trace?.payload?.name==='ext_dateTimeCaptureEnd',render:({element})=>{TRACK.trackEvent('ext_dateTimeCaptureEnd');forceRemoveWrapper(element);return()=>{}}}; const ext_fullNameCaptured={name:'ext_fullNameCaptured',type:'response',match:({trace})=>trace?.type==='ext_fullNameCaptured'||trace?.payload?.name==='ext_fullNameCaptured',render:({element})=>{TRACK.trackEvent('ext_fullNameCaptured');forceRemoveWrapper(element);return()=>{}}}; const ext_numberCaptured={name:'ext_numberCaptured',type:'response',match:({trace})=>trace?.type==='ext_numberCaptured'||trace?.payload?.name==='ext_numberCaptured',render:({element})=>{TRACK.trackEvent('ext_numberCaptured');forceRemoveWrapper(element);return()=>{}}}; const ext_finalDetailsStart={name:'ext_finalDetailsStart',type:'response',match:({trace})=>trace?.type==='ext_finalDetailsStart'||trace?.payload?.name==='ext_finalDetailsStart',render:({element})=>{TRACK.trackEvent('ext_finalDetailsStart');forceRemoveWrapper(element);return()=>{}}}; const ext_noEmail={name:'ext_noEmail',type:'response',match:({trace})=>trace?.type==='ext_noEmail'||trace?.payload?.name==='ext_noEmail',render:({element})=>{TRACK.trackEvent('ext_noEmail');forceRemoveWrapper(element);return()=>{}}}; const ext_emailCaptured={name:'ext_emailCaptured',type:'response',match:({trace})=>trace?.type==='ext_emailCaptured'||trace?.payload?.name==='ext_emailCaptured',render:({element})=>{TRACK.trackEvent('ext_emailCaptured');forceRemoveWrapper(element);return()=>{}}}; const ext_noBudget={name:'ext_noBudget',type:'response',match:({trace})=>trace?.type==='ext_noBudget'||trace?.payload?.name==='ext_noBudget',render:({element})=>{TRACK.trackEvent('ext_noBudget');forceRemoveWrapper(element);return()=>{}}}; const ext_budgetCaptured={name:'ext_budgetCaptured',type:'response',match:({trace})=>trace?.type==='ext_budgetCaptured'||trace?.payload?.name==='ext_budgetCaptured',render:({element})=>{TRACK.trackEvent('ext_budgetCaptured');forceRemoveWrapper(element);return()=>{}}}; const ext_noDetails={name:'ext_noDetails',type:'response',match:({trace})=>trace?.type==='ext_noDetails'||trace?.payload?.name==='ext_noDetails',render:({element})=>{TRACK.trackEvent('ext_noDetails');forceRemoveWrapper(element);return()=>{}}}; const ext_noPhotosUploaded={name:'ext_noPhotosUploaded',type:'response',match:({trace})=>trace?.type==='ext_noPhotosUploaded'||trace?.payload?.name==='ext_noPhotosUploaded',render:({element})=>{TRACK.trackEvent('ext_noPhotosUploaded');forceRemoveWrapper(element);return()=>{}}}; const ext_photosUploaded={name:'ext_photosUploaded',type:'response',match:({trace})=>trace?.type==='ext_photosUploaded'||trace?.payload?.name==='ext_photosUploaded',render:({element})=>{TRACK.trackEvent('ext_photosUploaded');forceRemoveWrapper(element);return()=>{}}}; const ext_finalDetailsCaptured={name:'ext_finalDetailsCaptured',type:'response',match:({trace})=>trace?.type==='ext_finalDetailsCaptured'||trace?.payload?.name==='ext_finalDetailsCaptured',render:({element})=>{TRACK.trackEvent('ext_finalDetailsCaptured');forceRemoveWrapper(element);return()=>{}}}; const ext_jobPostSuccess={name:'ext_jobPostSuccess',type:'response',match:({trace})=>trace?.type==='ext_jobPostSuccess'||trace?.payload?.name==='ext_jobPostSuccess',render:({element})=>{TRACK.trackEvent('ext_jobPostSuccess');forceRemoveWrapper(element);return()=>{}}}; const ext_geyserStarted={name:'ext_geyserStarted',type:'response',match:({trace})=>trace?.type==='ext_geyserStarted'||trace?.payload?.name==='ext_geyserStarted',render:({element})=>{TRACK.trackEvent('ext_geyserStarted');forceRemoveWrapper(element);return()=>{}}}; const ext_geyserDiagnosticCompleted={name:'ext_geyserDiagnosticCompleted',type:'response',match:({trace})=>trace?.type==='ext_geyserDiagnosticCompleted'||trace?.payload?.name==='ext_geyserDiagnosticCompleted',render:({element})=>{TRACK.trackEvent('ext_geyserDiagnosticCompleted');forceRemoveWrapper(element);return()=>{}}}; const ext_geyserBookingStart={name:'ext_geyserBookingStart',type:'response',match:({trace})=>trace?.type==='ext_geyserBookingStart'||trace?.payload?.name==='ext_geyserBookingStart',render:({element})=>{TRACK.trackEvent('ext_geyserBookingStart');forceRemoveWrapper(element);return()=>{}}}; /* ==================== LOADING EXTENSIONS ==================== */ const LO={name:'ext_loading_on',type:'response',match:({trace})=>trace?.type==='ext_loading_on'||trace?.payload?.name==='ext_loading_on',render:({trace,element})=>{const p=__vfParsePayload(trace),label=p.label||'Confirming your booking details',minMs=Math.max(1000,Number(p.min_ms||0));CH.hide();MZ.enable();const w=d.createElement('div');w.className='vf-anti-zoom vf-wide-card vf-loader-card';w.setAttribute('role','status');w.setAttribute('aria-live','polite');w.setAttribute('aria-busy','true');w.innerHTML=`
Please wait a moment…
${label}
`;element.appendChild(w);stretch(element,w);AH.hideFor(w);__VF_LOADERS.add({node:w,contentEl:element,wrapper:vfClosestWrapper(element),started:Date.now(),minMs});return()=>{}}}; const LF={name:'ext_loading_off',type:'response',match:({trace})=>trace?.type==='ext_loading_off'||trace?.payload?.name==='ext_loading_off',render:()=>{__vfRemoveAllLoaders();return()=>{}}}; const LO1={name:'ext_loading_on_first',type:'response',match:({trace})=>trace?.type==='ext_loading_on_first'||trace?.payload?.name==='ext_loading_on_first',render:({trace,element})=>{const p=__vfParsePayload(trace),label=p.label||'Please wait a moment...',minMs=Math.max(1000,Number(p.min_ms||0));CH.hide();MZ.enable();const w=d.createElement('div');w.className='vf-anti-zoom vf-wide-card vf-loader-card';w.setAttribute('role','status');w.setAttribute('aria-live','polite');w.setAttribute('aria-busy','true');w.innerHTML=`
Looking for nearby Pros
${label}
`;element.appendChild(w);stretch(element,w);AH.hideFor(w);__VF_LOADERS.add({node:w,contentEl:element,wrapper:vfClosestWrapper(element),started:Date.now(),minMs});return()=>{}}}; const LF1={name:'ext_loading_off_first',type:'response',match:({trace})=>trace?.type==='ext_loading_off_first'||trace?.payload?.name==='ext_loading_off_first',render:()=>{__vfRemoveAllLoaders();return()=>{}}}; /* ==================== SHARED HELPERS ==================== */ const SF=(()=>{function hideBoth(a,b){if(a)a.classList.add('vf-savefx-hide');if(b)b.classList.add('vf-savefx-hide');setTimeout(()=>{if(a)a.style.display='none';if(b)b.style.display='none'},320)}return{celebrate(btn,sib){if(!btn)return;btn.classList.add('vf-savefx-success','vf-savefx-bounce');try{const r=btn.getBoundingClientRect();if(typeof confetti=='function')confetti({origin:{x:(r.left+r.width/2)/innerWidth,y:(r.top+r.height/2)/innerHeight},particleCount:24,spread:54,startVelocity:22,ticks:120,scalar:.8,zIndex:999999999})}catch(e){}setTimeout(()=>btn.classList.remove('vf-savefx-bounce'),240);setTimeout(()=>hideBoth(btn,sib),1e3)},hideBoth}})(); const stretch=(el,wrap)=>{try{el.style.width=el.style.maxWidth='100%';el.style.alignSelf='stretch'}catch(e){}try{wrap.style.width=wrap.style.maxWidth='100%'}catch(e){}let p=el.parentElement;for(let i=0;i<4&&p;i++){try{if(p.style){p.style.width=p.style.maxWidth='100%';p.style.flex='1 1 100%';p.style.alignSelf='stretch'}}catch(e){}p=p.parentElement||(p.getRootNode&&p.getRootNode().host)||null}}; const currencyR=n=>typeof n=='number'?'R '+n.toString().replace(/\B(?=(\d{3})+(?!\d))/g,' '):n,setVar=(k,v)=>{try{V?.setVariable?.(k,v)}catch(e){}}; const makeShell=({label,description,htmlField,submitText='Save',errorText='Please fix the error above.'})=>{const w=d.createElement('div');w.className='vf-wide-card';w.innerHTML=`
${description?`

${description}

`:''}${htmlField}
`;if(!d.getElementById('vf-anti-zoom-style')){const s=d.createElement('style');s.id='vf-anti-zoom-style';s.textContent='@media(max-width:1024px){.vf-anti-zoom input,.vf-anti-zoom textarea,.vf-anti-zoom select{font-size:16px!important;-webkit-text-size-adjust:100%}}';d.head.appendChild(s)}return w}; /* ==================== UI EXTENSIONS ==================== */ /* ---- Job Photos ---- */ const JPE={name:'jobPhotosExtension',type:'response',match:({trace})=>trace?.type==='jobPhotosExtension'||trace?.payload?.name==='jobPhotosExtension',render:({element})=>{ CH.hide();MZ.enable(); const shell=makeShell({label:'Image upload (optional)',description:'This helps your Pro assess and prepare. You can choose multiple files.',submitText:'Upload Photos',errorText:'Upload failed β€” please try again.',htmlField:`
πŸ“·
Image upload skipped
You can continue without photos
`}); element.appendChild(shell);stretch(element,shell);AH.hideFor(shell); const input=shell.querySelector('.vf-job-photos'),head=shell.querySelector('.vf-files-head'),list=shell.querySelector('.vf-file-list'),prog=shell.querySelector('.vf-progress'),bar=shell.querySelector('.vf-bar'),err=shell.querySelector('.vf-error'),save=shell.querySelector('.vf-submit'),skippedMsg=shell.querySelector('.vf-skipped-message'),label=shell.querySelector('label'),description=shell.querySelector('.vf-desc'); const row=d.createElement('div');row.style.cssText='display:flex;gap:8px;margin-top:10px'; const skip=d.createElement('button');skip.type='button';skip.className='vf-skip';skip.style.cssText='flex:1;padding:12px 16px;background:#0551fb;color:#fff;border:none;border-radius:6px;cursor:pointer;font-weight:600;font-size:16px';skip.textContent='Skip'; save.style.flex='1';save.style.marginTop='0';save.parentNode.insertBefore(row,save);row.appendChild(save);row.appendChild(skip); save.disabled=true;save.style.background='#9ca3af';save.style.cursor='not-allowed'; let files=[],imagePreviews=[]; const fmt=b=>{if(!b)return'0 Bytes';const k=1024,u=['Bytes','KB','MB','GB'];const i=Math.floor(Math.log(b)/Math.log(k));return(b/Math.pow(k,i)).toFixed(2)+' '+u[i]}; const enable=()=>{save.disabled=false;save.style.background='#0551fb';save.style.cursor='pointer';skip.style.background='#9ca3af'}; const disable=()=>{save.disabled=true;save.style.background='#9ca3af';save.style.cursor='not-allowed';skip.style.background='#0551fb'}; const generatePreviews=async(fileList)=>{imagePreviews=[];for(const file of fileList){if(file.type.startsWith('image/')){try{const dataUrl=await new Promise((resolve,reject)=>{const reader=new FileReader();reader.onload=e=>resolve(e.target.result);reader.onerror=reject;reader.readAsDataURL(file)});imagePreviews.push(dataUrl)}catch(e){}}}}; function renderList(){list.innerHTML='';if(!files.length){head.style.display='none';disable();return}head.style.display='block';files.forEach((f,i)=>{const row=d.createElement('div');row.className='vf-file-row';row.innerHTML=`
${f.name}
${fmt(f.size)} β€’ ${(f.type.split('/')[1]||'IMAGE').toUpperCase()}
`;list.appendChild(row)});list.querySelectorAll('.vf-remove').forEach(b=>b.onclick=()=>{const idx=+b.dataset.i;files.splice(idx,1);imagePreviews.splice(idx,1);renderList()});enable();requestAnimationFrame(()=>save.scrollIntoView({behavior:'smooth',block:'center'}))} input.onchange=async e=>{files=[...Array.from(e.target.files||[])];await generatePreviews(files);renderList()}; const showProg=()=>{prog.style.display='block';bar.style.width='0%'};const setProg=p=>{bar.style.width=p+'%'};const hideProg=()=>{prog.style.display='none'}; save.onclick=async()=>{if(!files.length)return;err.style.display='none';disable();input.disabled=true;showProg();save.textContent='Uploading…';try{const fd=new FormData();files.forEach(f=>fd.append('photos',f));let p=0;const t=setInterval(()=>{p=Math.min(96,p+Math.random()*18);setProg(Math.round(p))},160);const res=await fetch('https://europe-west1-kandua-manager.cloudfunctions.net/jobPhotoCapture',{method:'POST',body:fd});clearInterval(t);setProg(100);if(!res.ok)throw new Error('status '+res.status);const data=await res.json();hideProg();if(data.success){V.interact({type:'captured',payload:{photosUploaded:true,photoCount:files.length,uploadedFiles:data.uploadedImages||[],message:`${files.length} photos uploaded successfully`}});if(label)label.style.display='none';if(description)description.style.display='none';input.style.display='none';head.style.display='none';list.style.display='none';prog.style.display='none';err.style.display='none';row.style.display='none';const successMsg=d.createElement('div');successMsg.className='vf-skipped-message';successMsg.style.display='block';let imagePreviewsHtml='';if(imagePreviews.length>0){const imageElements=imagePreviews.map(preview=>`Uploaded photo`).join('');imagePreviewsHtml=`
${imageElements}
`}successMsg.innerHTML=`
βœ…
Upload successful
${files.length} photo${files.length!==1?'s':''} uploaded
${imagePreviewsHtml}`;shell.querySelector('.vf-form-card').appendChild(successMsg);successMsg.scrollIntoView({behavior:'smooth',block:'center'});CH.show();MZ.disable()}else throw new Error(data.message||'Upload failed')}catch(e){hideProg();save.textContent='Upload Photos';enable();input.disabled=false;err.textContent='Upload failed β€” please try again.';err.style.display='block'}}; skip.onclick=()=>{if(label)label.style.display='none';if(description)description.style.display='none';input.style.display='none';head.style.display='none';list.style.display='none';prog.style.display='none';err.style.display='none';row.style.display='none';skippedMsg.style.display='block';skippedMsg.scrollIntoView({behavior:'smooth',block:'center'});V.interact({type:'captured',payload:{photosUploaded:false,photoCount:0,uploadedFiles:[],skipped:true}});CH.show();MZ.disable()}; return()=>{CH.show();MZ.disable()}}}; /* ---- Calendar ---- */ const CE={name:'calendar-picker',type:'response',match:({trace})=>trace?.type==='calendar'||trace?.payload?.name==='calendar-picker',render:({element})=>{CH.hide();MZ.enable();const shell=makeShell({label:'Preferred Date',description:'',submitText:'Confirm Date',errorText:'Please pick a date before confirming.',htmlField:`
Choose a date and then click "Confirm Date" to continue.
`});element.appendChild(shell);stretch(element,shell);AH.hideFor(shell);const calHost=shell.querySelector('.vf-calendar'),err=shell.querySelector('.vf-error'),save=shell.querySelector('.vf-submit');save.disabled=true;const picked=d.createElement('div');picked.className='vf-picked';picked.innerHTML='Date selected: ';save.insertAdjacentElement('afterend',picked);const pickedVal=picked.querySelector('.vf-picked-val'),fmtNice=iso=>{try{const[y,m,dd]=iso.split('-').map(Number),dt=new Date(y,m-1,dd);return dt.toLocaleDateString('en-GB',{weekday:'long',day:'numeric',month:'long',year:'numeric'})}catch{return iso}},toISODateLocal=dt=>{const y=dt.getFullYear(),m=String(dt.getMonth()+1).padStart(2,'0'),d2=String(dt.getDate()).padStart(2,'0');return`${y}-${m}-${d2}`},now=new Date,today=new Date(now.getFullYear(),now.getMonth(),now.getDate()),max=new Date(today.getFullYear()+1,11,31);let selectedISO=null;const fp=flatpickr(calHost,{inline:true,appendTo:calHost,monthSelectorType:'dropdown',minDate:today,maxDate:max,showMonths:1,disableMobile:true,dateFormat:'Y-m-d',defaultDate:null,onReady:(_d,_s,inst)=>{const h=calHost.querySelector('.flatpickr-current-month');if(h&&!h.querySelector('.vf-year-select')){const ys=d.createElement('select');ys.className='vf-year-select';for(let y=today.getFullYear();y<=max.getFullYear();y++){const o=d.createElement('option');o.value=o.textContent=String(y);ys.appendChild(o)}ys.value=String(inst.currentYear||today.getFullYear());ys.addEventListener('change',()=>inst.changeYear(+ys.value));h.appendChild(ys)}},onYearChange:(_d,_s,inst)=>{const ys=calHost.querySelector('.vf-year-select');if(ys)ys.value=String(inst.currentYear)},onMonthChange:(_d,_s,inst)=>{const ys=calHost.querySelector('.vf-year-select');if(ys)ys.value=String(inst.currentYear)},onChange:(_d,s)=>{selectedISO=s;if(s){save.disabled=false;err.style.display='none';picked.style.display='block';pickedVal.textContent=fmtNice(s)}}});shell.querySelectorAll('.vf-chip').forEach(b=>b.addEventListener('click',()=>{const q=b.getAttribute('data-q');let dt=new Date(today);if(q==='tomorrow')dt.setDate(dt.getDate()+1);else if(q==='in2')dt.setDate(dt.getDate()+2);else if(q==='weekend'){const day=dt.getDay(),delta=(6-day+7)%7;dt.setDate(dt.getDate()+delta)}else if(q==='nextweek'){const day=dt.getDay(),delta=(1-day+7)%7||7;dt.setDate(dt.getDate()+delta)}selectedISO=toISODateLocal(dt);try{fp?.setDate(dt,true)}catch(e){}save.disabled=false;err.style.display='none';picked.style.display='block';pickedVal.textContent=fmtNice(selectedISO)}));save.onclick=()=>{if(!selectedISO){err.style.display='block';return}err.style.display='none';SF.celebrate(save);['label','.vf-desc','.vf-cal-wrap'].forEach(s=>{const n=shell.querySelector(s);if(n)n.style.display='none'});picked.style.display='block';setVar('date',selectedISO);MZ.blurActive();CH.show();MZ.disable();V.interact({type:'captured',payload:{date:selectedISO,urgent:false}})};return()=>{CH.show();MZ.disable()}}}; /* ---- Location Picker ---- */ const LP={name:'ext_location_picker',type:'response',match:({trace})=>trace?.type==='ext_location_picker'||trace?.payload?.name==='ext_location_picker',render:({element})=>{ CH.hide();MZ.enable(); const shell=makeShell({label:'Where do you need the work done?',description:'Please enter your location below.',submitText:'Confirm Location',errorText:'Please select a location before continuing.',htmlField:'
Type at least 3 characters to see suggestions
Selected Location:
Getting your location...
'}); element.appendChild(shell);stretch(element,shell);AH.hideFor(shell); const currentBtn=shell.querySelector('[data-method="current"]'),searchBtn=shell.querySelector('[data-method="search"]'),input=shell.querySelector('.vf-location-input'),dropdownContainer=shell.querySelector('.vf-dropdown-container'),inputHelper=shell.querySelector('.vf-input-helper'),selectedDiv=shell.querySelector('.vf-selected-location'),selectedAddress=shell.querySelector('.vf-selected-address'),loading=shell.querySelector('.vf-location-loading'),error=shell.querySelector('.vf-error'),save=shell.querySelector('.vf-submit'),locationButtons=shell.querySelector('.vf-location-buttons'),autocompleteWrapper=shell.querySelector('.vf-autocomplete-wrapper'),label=shell.querySelector('label'),description=shell.querySelector('.vf-desc'); const fallbackLink=d.createElement('div');fallbackLink.className='vf-location-fallback';fallbackLink.innerHTML='Issues providing your location? Click here...';save.insertAdjacentElement('afterend',fallbackLink);const fallbackAnchor=fallbackLink.querySelector('a'); let selectedLocation=null,geocoder=null,autocompleteService=null,placesService=null,sessionToken=null,currentPredictions=[]; save.disabled=true;save.style.background='#9ca3af';save.style.cursor='not-allowed'; function enableSubmit(){save.disabled=false;save.style.background='#0551fb';save.style.cursor='pointer'} function showLoading(text){loading.querySelector('span').textContent=text||'Getting your location...';loading.style.display='flex'} function hideLoading(){loading.style.display='none'} function showError(message){error.textContent=message;error.style.display='block';hideLoading()} function hideError(){error.style.display='none'} function showSelected(location){selectedLocation=location;selectedAddress.textContent=location.formattedAddress;selectedDiv.classList.add('show');inputHelper.classList.remove('show');dropdownContainer.classList.remove('show');enableSubmit();hideError();hideLoading();if(window.innerWidth<=768){input.style.display='none';autocompleteWrapper.style.marginBottom='0'}requestAnimationFrame(()=>{save.scrollIntoView({behavior:'smooth',block:'center'})})} function extractSuburb(addressComponents){const suburbTypes=['sublocality_level_1','locality','administrative_area_level_3'];for(const type of suburbTypes){const component=addressComponents.find(comp=>comp.types.includes(type));if(component)return component.long_name}return''} function renderPredictions(predictions){dropdownContainer.innerHTML='';if(!predictions||predictions.length===0){dropdownContainer.classList.remove('show');return}const limitedPredictions=predictions.slice(0,2);currentPredictions=limitedPredictions;limitedPredictions.forEach((prediction,index)=>{const item=d.createElement('div');item.className='vf-prediction-item';item.style.cssText='padding:16px;cursor:pointer;border-bottom:1px solid #e5e7eb;display:flex;align-items:center;gap:12px;-webkit-tap-highlight-color:transparent;transition:background 0.15s ease';const icon=d.createElement('div');icon.style.cssText='width:20px;height:20px;flex-shrink:0;color:#6b7280';icon.innerHTML='πŸ“';item.appendChild(icon);const textContainer=d.createElement('div');textContainer.style.cssText='flex:1;min-width:0';const mainText=d.createElement('div');mainText.style.cssText='font-weight:600;color:#111827;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis';mainText.textContent=prediction.structured_formatting.main_text;const secondaryText=d.createElement('div');secondaryText.style.cssText='color:#6b7280;font-size:13px;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis';secondaryText.textContent=prediction.structured_formatting.secondary_text;textContainer.appendChild(mainText);textContainer.appendChild(secondaryText);item.appendChild(textContainer);if(index===limitedPredictions.length-1)item.style.borderBottom='none';item.addEventListener('mouseenter',()=>{item.style.background='#eff6ff'});item.addEventListener('mouseleave',()=>{item.style.background='white'});item.addEventListener('click',()=>{selectPrediction(prediction)});item.addEventListener('touchstart',()=>{item.style.background='#dbeafe'},{passive:true});item.addEventListener('touchend',()=>{item.style.background='#eff6ff'},{passive:true});dropdownContainer.appendChild(item)});dropdownContainer.classList.add('show')} function selectPrediction(prediction){if(!placesService)return;showLoading('Getting location details...');dropdownContainer.classList.remove('show');placesService.getDetails({placeId:prediction.place_id,fields:['address_components','formatted_address','geometry','name'],sessionToken:sessionToken},(place,status)=>{hideLoading();if(status===google.maps.places.PlacesServiceStatus.OK&&place){const location={formattedAddress:place.formatted_address,suburb:extractSuburb(place.address_components||[]),lat:place.geometry.location.lat(),lng:place.geometry.location.lng()};input.value=place.formatted_address;showSelected(location);sessionToken=new google.maps.places.AutocompleteSessionToken()}else{showError('Could not get location details. Please try another address.')}})} function loadGoogleMaps(){return new Promise((resolve,reject)=>{if(window.google&&window.google.maps){resolve();return}const script=d.createElement('script');script.src='https://maps.googleapis.com/maps/api/js?key=AIzaSyAsmsHobVLVfqzS_0JvE7NIPpWZiTLfOic&libraries=places';script.onload=()=>resolve();script.onerror=()=>reject(new Error('Failed to load Google Maps'));d.head.appendChild(script)})} async function initializeGoogleMaps(){try{await loadGoogleMaps();geocoder=new google.maps.Geocoder();autocompleteService=new google.maps.places.AutocompleteService();const placesDiv=d.createElement('div');placesDiv.style.display='none';d.body.appendChild(placesDiv);placesService=new google.maps.places.PlacesService(placesDiv);sessionToken=new google.maps.places.AutocompleteSessionToken();setupAutocomplete()}catch(err){showError('Failed to load location services. Please refresh and try again.')}} function setupAutocomplete(){let searchTimeout;input.addEventListener('input',()=>{const value=input.value.trim();clearTimeout(searchTimeout);if(value.length>0&&value.length<3){inputHelper.textContent=`Type ${3-value.length} more character${3-value.length===1?'':'s'}`;inputHelper.classList.add('show');dropdownContainer.classList.remove('show');return}else if(value.length===0){inputHelper.classList.remove('show');dropdownContainer.classList.remove('show');return}inputHelper.textContent='Select an address from the suggestions below';inputHelper.classList.add('show');searchTimeout=setTimeout(()=>{if(value.length>=3)performSearch(value)},300)});input.addEventListener('keydown',(e)=>{if(e.key===' '||e.keyCode===32)e.stopPropagation()});d.addEventListener('click',(e)=>{if(!autocompleteWrapper.contains(e.target))dropdownContainer.classList.remove('show')})} function performSearch(query){if(!autocompleteService)return;autocompleteService.getPlacePredictions({input:query,componentRestrictions:{country:'za'},types:['address'],sessionToken:sessionToken},(predictions,status)=>{if(status===google.maps.places.PlacesServiceStatus.OK&&predictions){renderPredictions(predictions)}else if(status===google.maps.places.PlacesServiceStatus.ZERO_RESULTS){dropdownContainer.innerHTML='
No addresses found. Try a different search.
';dropdownContainer.classList.add('show')}else{dropdownContainer.classList.remove('show')}})} async function getCurrentLocation(){if(!navigator.geolocation){showError('Geolocation is not supported by your browser.');return}if(location.protocol!=='https:'&&location.hostname!=='localhost'){showError('Location access requires a secure (HTTPS) connection.');return}if(!geocoder){showError('Location services are still loading. Please try again in a moment.');return}showLoading('Getting your current location...');hideError();try{const position=await new Promise((resolve,reject)=>{navigator.geolocation.getCurrentPosition(resolve,reject,{enableHighAccuracy:true,timeout:15000,maximumAge:300000})});const lat=position.coords.latitude,lng=position.coords.longitude;showLoading('Finding your address...');const response=await new Promise((resolve,reject)=>{geocoder.geocode({location:{lat,lng}},(results,status)=>{if(status==='OK'&&results&&results.length>0)resolve(results[0]);else reject(new Error(`Geocoding failed: ${status}`))})});const location={formattedAddress:response.formatted_address,suburb:extractSuburb(response.address_components||[]),lat:lat,lng:lng};showSelected(location)}catch(error){hideLoading();let errorMessage='Could not get your location: ';if(error.code){switch(error.code){case error.PERMISSION_DENIED:errorMessage+='Location access was denied. Please enable location access in your browser settings.';break;case error.POSITION_UNAVAILABLE:errorMessage+='Your location information is unavailable. Please try entering your address manually.';break;case error.TIMEOUT:errorMessage+='Location request timed out. Please try again or enter your address manually.';break;default:errorMessage+='An unknown error occurred. Please try entering your address manually.'}}else{errorMessage='Could not determine your address. Please enter it manually.'}showError(errorMessage)}} currentBtn.addEventListener('click',()=>{currentBtn.classList.add('active');searchBtn.classList.remove('active');input.style.display='none';dropdownContainer.classList.remove('show');inputHelper.classList.remove('show');selectedDiv.classList.remove('show');getCurrentLocation()}); searchBtn.addEventListener('click',()=>{searchBtn.classList.add('active');currentBtn.classList.remove('active');input.style.display='block';input.value='';dropdownContainer.classList.remove('show');selectedDiv.classList.remove('show');selectedLocation=null;save.disabled=true;save.style.background='#9ca3af';hideLoading();hideError();setTimeout(()=>{input.focus()},100)}); fallbackAnchor.addEventListener('click',(e)=>{e.preventDefault();V.interact({type:'captured',payload:{locationFallback:true}});MZ.blurActive();CH.show();MZ.disable()}); save.onclick=()=>{if(!selectedLocation){showError('Please select a location before continuing.');return}hideError();SF.celebrate(save);setTimeout(()=>{if(label)label.style.display='none';if(description)description.style.display='none';if(locationButtons)locationButtons.style.display='none';if(autocompleteWrapper)autocompleteWrapper.style.display='none';if(loading)loading.style.display='none';if(error)error.style.display='none';if(save)save.style.display='none';if(fallbackLink)fallbackLink.style.display='none';if(inputHelper)inputHelper.style.display='none'},1000);setVar('formattedAddress',selectedLocation.formattedAddress);setVar('suburb',selectedLocation.suburb);setVar('googleLat',selectedLocation.lat.toString());setVar('googleLong',selectedLocation.lng.toString());MZ.blurActive();CH.show();MZ.disable();V.interact({type:'captured',payload:{formattedAddress:selectedLocation.formattedAddress,suburb:selectedLocation.suburb,latitude:selectedLocation.lat,longitude:selectedLocation.lng,locationConfirmed:true}})}; initializeGoogleMaps(); return()=>{CH.show();MZ.disable()}}}; /* ---- Booking Summary ---- */ const BS={name:'ext_booking_summary',type:'response',match:({trace})=>{const t=trace?.type,n=trace?.payload?.name;return t==='ext_booking_summary'||n==='ext_booking_summary'||t==='booking_summary'||n==='booking_summary'},render:({trace,element})=>{ CH.hide();MZ.enable(); const p=(()=>{try{return typeof trace?.payload==='string'?JSON.parse(trace.payload):(trace?.payload||{})}catch(_){return{}}})(); const jobDiagnosed=p.job_diagnosed??'{{job_diagnosed}}',formattedAddress=p.formattedAddress??'{{formattedAddress}}',dateRequest=p.date_request??'{{date_request}}',timeDisplayed=p.time_displayed??'{{time_displayed}}',userPhone=p.user_phone??'{{user_phone}}'; const shell=d.createElement('div');shell.className='vf-wide-card'; shell.innerHTML=`
Booking Summary
πŸ”§
Need help with:
${jobDiagnosed||'Not specified'}
πŸ“
Address:
${formattedAddress||'Not specified'}
πŸ“…
Date and Time:
${(dateRequest||'Not specified')} / ${(timeDisplayed||'Not specified')}
πŸ“ž
Phone Number:
${userPhone||'Not specified'}
By confirming your booking, you agree to Kandua's Terms of Service and consent to us sharing your job details with a trusted and vetted service provider. Booking subject to Pro availability
`; element.appendChild(shell);stretch(element,shell);AH.hideFor(shell); const confirmBtn=shell.querySelector('.vf-summary-confirm'),changeBtn=shell.querySelector('.vf-summary-change'),buttonsContainer=shell.querySelector('.vf-summary-buttons'); confirmBtn.onclick=()=>{setVar('confirmation','confirm');SF.celebrate(confirmBtn,changeBtn);setTimeout(()=>{buttonsContainer.style.display='none'},1000);MZ.blurActive();CH.show();MZ.disable();V.interact({type:'captured',payload:{confirmation:'confirm',action:'booking_confirmed'}})}; changeBtn.onclick=()=>{setVar('confirmation','update');setTimeout(()=>{buttonsContainer.style.display='none'},1000);MZ.blurActive();CH.show();MZ.disable();V.interact({type:'captured',payload:{confirmation:'update',action:'change_details'}})}; return()=>{CH.show();MZ.disable()}}}; /* ---- Confetti ---- */ const XE={name:'confetti',type:'response',match:({trace})=>trace?.type==='booking_complete',render:({element})=>{try{confetti({particleCount:100,spread:60,gravity:.6,ticks:300,zIndex:999999999})}catch(e){}setTimeout(()=>{try{confetti({particleCount:80,spread:100,gravity:.6,ticks:300,zIndex:999999999})}catch(e){}},200);setTimeout(()=>{removeIfEmptyWrapper(element)},10);return()=>{}}}; /* ---- Full Name ---- */ const fNm={name:'ext_fullName',type:'response',match:({trace})=>trace?.type==='ext_fullName'||trace?.payload?.name==='ext_fullName',render:({trace,element})=>{CH.hide();MZ.enable();const p=typeof trace.payload==='string'?JSON.parse(trace.payload):(trace.payload||{}),ph=p.placeholder||'John Smith',sh=makeShell({label:'Full Name',description:'Please enter your first and last name.',htmlField:``,errorText:"Please enter both your first and last name (letters, spaces, hyphens and apostrophes only)."});element.appendChild(sh);stretch(element,sh);AH.hideFor(sh);const inp=sh.querySelector('.vf-input'),err=sh.querySelector('.vf-error'),save=sh.querySelector('.vf-submit'),validate=v=>{const trimmed=v.trim();if(!trimmed)return{valid:false,error:'Please enter your full name.'};if(!/^[A-Za-zΓ€-Γ–Γ˜-ΓΆΓΈ-ΓΏ' -]+$/.test(trimmed))return{valid:false,error:'Name can only contain letters, spaces, hyphens and apostrophes.'};const words=trimmed.split(/\s+/).filter(w=>w.length>0);if(words.length<2)return{valid:false,error:'Please enter both your first and last name.'};if(trimmed.length>100)return{valid:false,error:'Name is too long. Please enter a shorter name.'};return{valid:true,words}},splitName=words=>{const firstName=words[0],lastName=words[words.length-1],fullName=words.join(' ');return{firstName,lastName,fullName}};save.onclick=()=>{const v=inp.value.trim(),validation=validate(v);if(!validation.valid){err.textContent=validation.error;err.style.display='block';return}err.style.display='none';const{firstName,lastName,fullName}=splitName(validation.words);SF.celebrate(save);setVar('firstName',firstName);setVar('lastName',lastName);setVar('fullName',fullName);MZ.blurActive();CH.show();MZ.disable();V.interact({type:'captured',payload:{firstName,lastName,fullName}})};return()=>{CH.show();MZ.disable()}}}; /* ---- Phone ---- */ const pN={name:'ext_phone',type:'response',match:({trace})=>trace?.type==='ext_phone'||trace?.payload?.name==='ext_phone',render:({element})=>{CH.hide();MZ.enable();const sh=makeShell({label:'Phone Number',description:"Please enter a valid, 10 digit South African phone number like 082 123 4567 - We'll add +27",htmlField:``,errorText:'Please enter a 10 digit number starting with 0.'});element.appendChild(sh);stretch(element,sh);AH.hideFor(sh);const tel=sh.querySelector('.vf-phone'),err=sh.querySelector('.vf-error'),save=sh.querySelector('.vf-submit');tel.addEventListener('keydown',e=>{if(e.key===' '||e.key==='Spacebar')e.preventDefault()});tel.addEventListener('input',()=>{tel.value=tel.value.replace(/\s+/g,'')});tel.addEventListener('paste',e=>{e.preventDefault();const t=(e.clipboardData||window.clipboardData).getData('text')||'';tel.value=t.replace(/\D/g,'')});function validate(raw){const d=(raw||'').replace(/\D/g,'');if(!/^0\d{9}$/.test(d)){const n=d.length;err.textContent=`Please enter a 10 digit number starting with 0 (you entered ${n} digit${n===1?'':'s'}).`;return null}return d}save.onclick=()=>{const dgt=validate(tel.value);if(!dgt){err.style.display='block';return}err.style.display='none';const e164='+27'+dgt.slice(1);SF.celebrate(save);setVar('userPhone',e164);MZ.blurActive();CH.show();MZ.disable();V.interact({type:'captured',payload:{userPhone:e164}})};return()=>{CH.show();MZ.disable()}}}; /* ---- Email ---- */ const eM={name:'ext_email',type:'response',match:({trace})=>trace?.type==='ext_email'||trace?.payload?.name==='ext_email',render:({trace,element})=>{CH.hide();MZ.enable();const p=typeof trace.payload==='string'?JSON.parse(trace.payload):(trace.payload||{}),ph=p.placeholder||'email@me.co.za',w=d.createElement('div');w.className='vf-anti-zoom vf-wide-card';w.innerHTML=`
`;element.appendChild(w);stretch(element,w);AH.hideFor(w);const inp=w.querySelector('.vf-input'),err=w.querySelector('.vf-error'),save=w.querySelector('.vf-save'),skip=w.querySelector('.vf-skip'),isEmail=v=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);save.onclick=()=>{const v=(inp.value||'').trim();if(!v||!isEmail(v)){err.style.display='block';return}err.style.display='none';SF.celebrate(save,skip);setVar('userEmail',v);MZ.blurActive();CH.show();MZ.disable();V.interact({type:'captured',payload:{userEmail:v,skipped:false}})};skip.onclick=()=>{err.style.display='none';SF.hideBoth(save,skip);setVar('userEmail','');MZ.blurActive();CH.show();MZ.disable();V.interact({type:'captured',payload:{userEmail:'',skipped:true}})};return()=>{CH.show();MZ.disable()}}}; /* ---- Budget ---- */ const bG={name:'ext_budget',type:'response',match:({trace})=>trace?.type==='ext_budget'||trace?.payload?.name==='ext_budget',render:({element})=>{CH.hide();MZ.enable();const w=d.createElement('div');w.className='vf-anti-zoom vf-wide-card';w.innerHTML=`

Slide to select your approximate budget.

R 300R 5 000+
`;element.appendChild(w);stretch(element,w);AH.hideFor(w);const sl=w.querySelector('.vf-range'),ro=w.querySelector('.vf-readout'),sv=w.querySelector('.vf-save'),sk=w.querySelector('.vf-skip');let used=false;const fmt=v=>Number(v)>=5000?'R 5 000+':currencyR(Number(v)),en=()=>{sv.disabled=false;sv.style.background='#0551fb';sv.style.cursor='pointer';sk.style.background='#9ca3af';sk.style.cursor='pointer'},show=()=>{ro.style.display='block';ro.textContent=fmt(sl.value)},first=()=>{if(used)return;used=true;en();show()};sl.addEventListener('input',()=>{first();ro.textContent=fmt(sl.value)});sv.onclick=()=>{if(sv.disabled)return;const v=Number(sl.value),out=v>=5000?'R 5 000+':currencyR(v);SF.celebrate(sv,sk);setVar('budget',out);MZ.blurActive();CH.show();MZ.disable();V.interact({type:'captured',payload:{budget:out,skipped:false}})};sk.onclick=()=>{SF.hideBoth(sv,sk);setVar('budget','');MZ.blurActive();CH.show();MZ.disable();V.interact({type:'captured',payload:{budget:'',skipped:true}})};return()=>{CH.show();MZ.disable()}}}; /* ---- Additional Details ---- */ const aD={name:'ext_additionalDetails',type:'response',match:({trace})=>trace?.type==='ext_additionalDetails'||trace?.payload?.name==='ext_additionalDetails',render:({trace,element})=>{CH.hide();MZ.enable();const p=typeof trace.payload==='string'?JSON.parse(trace.payload):(trace.payload||{}),ph=p.placeholder||'Ring unit 19 at the gate...',w=d.createElement('div');w.className='vf-anti-zoom vf-wide-card';w.innerHTML=`
Anything the pro should know before arriving.
`;element.appendChild(w);stretch(element,w);AH.hideFor(w);const ta=w.querySelector('.vf-textarea'),sv=w.querySelector('.vf-save'),sk=w.querySelector('.vf-skip');sv.onclick=()=>{const v=(ta.value||'').trim();SF.celebrate(sv,sk);setVar('additionalDetails',v);MZ.blurActive();CH.show();MZ.disable();V.interact({type:'captured',payload:{additionalDetails:v,skipped:false}})};sk.onclick=()=>{SF.hideBoth(sv,sk);setVar('additionalDetails','');MZ.blurActive();CH.show();MZ.disable();V.interact({type:'captured',payload:{additionalDetails:'',skipped:true}})};return()=>{CH.show();MZ.disable()}}}; /* ---- Progress Steps ---- */ function makeProgressExt({name,step,title,subtitle}){return{name,type:'response',match:({trace})=>trace?.type===name||trace?.payload?.name===name,render:({element})=>{const pct=Math.min(100,Math.max(0,step*25)),w=d.createElement('div');w.className='vf-wide-card';w.innerHTML=`
Step ${step} of 4 β€” ${title}
0%
1
2
3
4
${subtitle?`
${subtitle}
`:''}
`;element.appendChild(w);stretch(element,w);AH.hideFor(w);const pctEl=w.querySelector('.vf-progress-pct'),fill=w.querySelector('.vf-progress-fill'),dots=[...w.querySelectorAll('.vf-step-dot')];dots.forEach((dot,i)=>{const idx=i+1;dot.classList.remove('complete','current');if(idx1-Math.pow(1-t,3);(function f(n){const t=Math.min(1,(n-st)/dur),v=Math.round(sv+dl*ez(t));fill.style.width=v+'%';pctEl.textContent=v+'%';if(t<1)requestAnimationFrame(f)})(st)}}} const P1=makeProgressExt({name:'ext_progress_step1',step:1,title:'Problem diagnosis',subtitle:'We are understanding your issue.'}), P2=makeProgressExt({name:'ext_progress_step2',step:2,title:'Location',subtitle:'Where do you need help?'}), P3=makeProgressExt({name:'ext_progress_step3',step:3,title:'Personal details',subtitle:'We are almost there.'}), P4=makeProgressExt({name:'ext_progress_step4',step:4,title:'Done',subtitle:'All set!'}); /* ---- Pros Found ---- */ const PF={name:'ext_pros_found',type:'response',match:({trace})=>trace?.type==='ext_pros_found'||trace?.payload?.name==='ext_pros_found',render:({trace,element})=>{const p=typeof trace?.payload==='string'?(()=>{try{return JSON.parse(trace.payload)}catch(e){return{}}})():(trace?.payload||{}),c=Number(p?.count)||3,a=p?.area||'';setVar('prosFound',true);setVar('prosFoundCount',c);const w=d.createElement('div');w.className='vf-wide-card';w.innerHTML=`
β˜…β˜…β˜…β˜…β˜…β˜…
Great news!
We have found top-rated, vetted Pros ${a?`near ${a}`:'near you'}.
All Kandua Pros have been pre-vetted and are rated 4.5β˜… or higher. No need to worry about choosing, we will make sure the right Pro gets your job βœ….
`;element.appendChild(w);stretch(element,w);AH.hideFor(w);try{const hero=w.querySelector('.vf-hero');if(hero&&hero.style){hero.style.flexShrink='0';hero.style.minWidth=hero.style.minWidth||'72px';hero.style.minHeight=hero.style.minHeight||'72px'}}catch(e){}try{const pth=w.querySelector('.vf-check path'),L=pth.getTotalLength();pth.style.strokeDasharray=String(L);pth.style.strokeDashoffset=String(L);requestAnimationFrame(()=>{pth.style.transition='stroke-dashoffset 700ms cubic-bezier(.22,1,.36,1)';pth.style.strokeDashoffset='0'})}catch(e){}}}; /* ==================== GEYSER EXTENSIONS ==================== */ /* ---- Geyser Welcome ---- */ const GeyserWelcomeExtension={name:'GeyserWelcome',type:'response',match:({trace})=>trace?.type==='ext_geyserWelcome'||(trace?.payload?.name==='ext_geyserWelcome'),render:({element})=>{ const container=d.createElement('div');container.className='vf-wide-card'; container.innerHTML=`
Geyser health check
Answer 9 quick questions about your geyser, and Jess will assess your risk level.
Why this matters

Geysers are South Africa's #1 home insurance claim. The average burst geyser costs R15,000–R50,000+ in damage β€” and most failures are preventable.

What you'll get
Personalised risk assessment
Maintenance recommendations
Book a vetted geyser pro
Takes about 2 minutes
`; element.appendChild(container);AH.hideFor(container);CH.hide(); container.querySelector('.jgw-btn').addEventListener('click',()=>{ const card=container.querySelector('.jgw-card');if(card)card.classList.add('jgw-exit'); setTimeout(()=>{container.style.overflow='hidden';container.style.maxHeight=container.scrollHeight+'px';container.offsetHeight;container.style.transition='max-height 0.35s ease,opacity 0.35s ease';container.style.maxHeight='0';container.style.opacity='0'},350); setTimeout(()=>{try{container.remove()}catch(e){}CH.show();V.interact({type:'start_diagnostic',payload:{}})},700) }); return()=>{CH.hide()}}}; /* ---- Geyser Results ---- */ const GeyserResultsExtension={name:'GeyserResults',type:'response',match:({trace})=>trace?.type==='ext_geyserResults'||(trace?.payload?.name==='ext_geyserResults'),render:({trace,element})=>{ const p=trace?.payload||{},zone=p.zone||'caution',findings=p.findings||''; const zones={danger:{accent:'linear-gradient(90deg,#E24B4A,#D85A30)',color:'#E24B4A',chipBg:'#FCEBEB',chipText:'#791F1F',icon:'',headline:'Your geyser needs urgent attention',subtext:'Based on your answers, there are a few things that really concern me. Here\'s what stood out:',nudge:'I\'d strongly suggest getting a plumber out this week β€” the longer you wait, the bigger the risk.',segments:4},caution:{accent:'linear-gradient(90deg,#EF9F27,#BA7517)',color:'#EF9F27',chipBg:'#FAEEDA',chipText:'#633806',icon:'',headline:'A few things worth keeping an eye on',subtext:'Nothing alarming right now, but there are some signs your geyser could use a check-up:',nudge:'I\'d recommend booking a geyser service in the next month or two β€” prevention is always cheaper than repair.',segments:3},safe:{accent:'linear-gradient(90deg,#97C459,#639922)',color:'#639922',chipBg:'#EAF3DE',chipText:'#27500A',icon:'',headline:'Looking good β€” your geyser\'s in great shape',subtext:'Based on your answers, there\'s nothing to worry about right now. Here\'s what\'s working in your favour:',nudge:'Pop back in about 12 months and we\'ll run another check. In the meantime, you\'re in a great spot.',segments:1}}; const z=zones[zone]||zones.caution,findingsList=findings.split(',').map(f=>f.trim()).filter(Boolean); let segmentsHTML='';for(let i=0;i<5;i++)segmentsHTML+=`
`; let chipsHTML='';findingsList.forEach((f,i)=>{chipsHTML+=`${f}`}); const container=d.createElement('div');container.className='vf-wide-card'; container.innerHTML=`
${z.icon}
${z.headline}
${segmentsHTML}
${z.subtext}
${chipsHTML}
${z.nudge}
`; element.appendChild(container);AH.hideFor(container);CH.hide(); container.querySelectorAll('.jgr-btn').forEach(btn=>{btn.addEventListener('click',()=>{CH.show();V.interact({type:btn.getAttribute('data-action'),payload:{}})})}); return()=>{CH.hide()}}}; /* ==================== LOAD VOICEFLOW ==================== */ V=window.voiceflow.chat; // Geyser-specific launch payload for santamboost.webflow.io const launchPayload={}; if(window.location.hostname==='kandua.com' && window.location.pathname==='/geyser-health-check'){ launchPayload.event='santamgeyser'; } V.load({ verify:{projectID:'6992f50e124345cdf1829f5a'}, url:'https://general-runtime.sanlamstudios.voiceflow.com', versionID:'production', launch:Object.keys(launchPayload).length?{event:{type:'launch',payload:launchPayload}}:undefined, assistant:{ persistence:'memory', extensions:[ // Loading LO1,LF1,LO,LF, // Progress & pros P1,P2,P3,P4,PF, // UI forms CE,JPE,XE,fNm,pN,eM,bG,aD,LP,BS, // Geyser diagnostic GeyserWelcomeExtension, GeyserResultsExtension, // GA4 tracking ext_chatInitiated, ext_conversationStarted, ext_mainIntent, ext_askJess, ext_supportBookPlumber, ext_supportBookElectrician, ext_electricianDiagnoseStart, ext_plumbingDiagnoseStart, ext_plumbingDiagnoseEnd, ext_electricianDiagnoseEnd, ext_serviceNotSupported, ext_proApplication, ext_locationStart, ext_locationFallback, ext_locationEnd, ext_noProsAvailable, ext_proCheckAPIFailed, ext_checkForProsSuccess, ext_dateTimeCaptureStart, ext_dateUrgent, ext_datePlanned, ext_dateTimeCaptureEnd, ext_fullNameCaptured, ext_numberCaptured, ext_finalDetailsStart, ext_noEmail, ext_emailCaptured, ext_noBudget, ext_budgetCaptured, ext_noDetails, ext_noPhotosUploaded, ext_photosUploaded, ext_finalDetailsCaptured, ext_jobPostSuccess, ext_geyserStarted, ext_geyserDiagnosticCompleted, ext_geyserBookingStart ] } }).then(()=>{ // Auto-open on page load V.open(); // Jess booking flow open buttons d.querySelectorAll('.open_v,.open_v_b').forEach(el=>el.addEventListener('click',()=>V.open())); // Geyser diagnostic open buttons d.querySelectorAll('.btn-upload').forEach(el=>el.addEventListener('click',e=>{e.preventDefault();V.open()})); }); }; d.body.appendChild(vf); } }(document);