1(()=>{const root=document.querySelector('#tsrb');if(!root)return;let step=1,state={requestId:'',contactAddressId:'',project:'',slotId:'',slotText:'',busy:false};const q=s=>root.querySelector(s),qa=s=>[...root.querySelectorAll(s)]; 2const show=n=>{step=n;qa('section[data-step]').forEach(x=>x.hidden=+x.dataset.step!==n);qa('.tsrb-progress span').forEach((x,i)=>x.classList.toggle('on',i<n));window.scrollTo({top:root.getBoundingClientRect().top+scrollY-120,behavior:'smooth'});}; 3const msg=(t,type='error')=>{let m=q('.tsrb-msg');m.textContent=t;m.className='tsrb-msg '+(type==='success'?'tsrb-msg-success':'');m.hidden=!t;};const val=n=>q(`[name="${n}"]`)?.value.trim()||''; 4const setBusy=(button,on,label='Workingâ¦')=>{if(!button)return; if(on){button.dataset.oldText=button.textContent;button.textContent=label;button.disabled=true;}else{button.textContent=button.dataset.oldText||button.textContent;button.disabled=false;}}; 5const friendlyError=()=> 'We couldnât complete that step right now. Your information may already be saved. Please try again, or call Top Shelf at (248) 617-4000.'; 6async function call(action,data){msg('');const fd=new URLSearchParams();fd.append('action','tsrb_'+action);fd.append('nonce',TSRB.nonce);Object.entries(data||{}).forEach(([k,v])=>{if(v&&typeof v==='object'&&!Array.isArray(v)){Object.entries(v).forEach(([ck,cv])=>fd.append(k+'['+ck+']',cv));}else fd.append(k,v??'');});let r=await fetch(TSRB.ajax,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:fd});let j;try{j=await r.json();}catch(e){throw Error(friendlyError());}if(!j.success)throw Error(j.data?.message||friendlyError());return j.data;} 7function fireLead(){if(window.fbq)fbq('track','Lead');if(window.dataLayer)window.dataLayer.push({event:'routemize_lead_created',routemize_request_id:state.requestId});} 8function fireRequest(){if(window.dataLayer)window.dataLayer.push({event:'routemize_scheduling_request',routemize_request_id:state.requestId});} 9function formatDate(value){if(!value)return'';const raw=String(value).trim();const m=raw.match(/^(\d{4})-(\d{2})-(\d{2})/);if(!m)return raw;const y=Number(m[1]),mo=Number(m[2]),d=Number(m[3]);const dt=new Date(y,mo-1,d);if(Number.isNaN(dt.getTime()))return raw;try{return new Intl.DateTimeFormat('en-US',{weekday:'long',month:'long',day:'numeric'}).format(dt);}catch(e){return raw;}} 10function formatTime(t){if(!t)return'';let [h,m]=t.split(':').map(Number),ap=h>=12?'PM':'AM';h=h%12||12;return `${h}:${String(m||0).padStart(2,'0')} ${ap}`;} 11function slotHour(s){return parseInt((s.startTime||'').split(':')[0],10);} 12function matchesDaypart(s,p){let h=slotHour(s);if(Number.isNaN(h)||p==='Flexible')return true;if(p==='Morning')return h<12;if(p==='Afternoon')return h>=12&&h<17;if(p==='Evening')return h>=17;return true;} 13qa('.tsrb-choice').forEach(b=>b.onclick=()=>{qa('.tsrb-choice').forEach(x=>x.classList.remove('selected'));b.classList.add('selected');state.project=b.dataset.value;q('[data-step="1"] .tsrb-next').disabled=false;});q('[data-step="1"] .tsrb-next').onclick=()=>show(2);qa('.tsrb-back').forEach(b=>b.onclick=()=>show(step-1)); 14q('.tsrb-capture').onclick=async e=>{const b=e.currentTarget;if(state.busy)return;state.busy=true;setBusy(b,true,'Savingâ¦');try{let d=await call('initiate',{firstName:val('firstName'),lastName:val('lastName'),email:val('email'),phone:val('phone')});state.requestId=d.requestId||d.updatedData?.requestId||d.data?.requestId||d.result?.requestId;if(!state.requestId)throw Error(friendlyError());fireLead();show(3);}catch(e){msg(e.message)}finally{state.busy=false;setBusy(b,false)}}; 15q('.tsrb-address').onclick=async e=>{const b=e.currentTarget;setBusy(b,true);try{let d=await call('address',{requestId:state.requestId,addressLine1:val('addressLine1'),addressLine2:val('addressLine2'),city:val('city'),state:val('state'),zipCode:val('zipCode')});state.contactAddressId=d.contactAddressId||d.updatedData?.contactAddressId;if(!state.contactAddressId)throw Error(friendlyError());renderQuestions();show(4);}catch(e){msg(e.message)}finally{setBusy(b,false)}}; 16function renderQuestions(){let box=q('.tsrb-dynamic'),html='';if(state.project==='Interior painting')html='<label>What needs painting inside?<select name="scope"><option>Whole or most of the house</option><option>A few rooms</option><option>Just one room or a small area</option></select></label>';if(state.project==='Exterior painting')html='<label>Whatâs the exterior scope?<select name="scope"><option>Whole house exterior</option><option>Trim & accents only</option><option>Deck, fence, or shed</option><option>Small Area</option><option>Brick Painting</option></select></label>';if(state.project==='Cabinet Project')html='<label>What type of cabinet project?<select name="scope" class="tsrb-cabinet-type"><option>Cabinet Refinishing (Painting)</option><option>Cabinet Replacement (Remodel)</option><option>Cabinet Refacing (New Doors / Drawers)</option></select></label><div class="tsrb-cabinet-refinish"><label>Which cabinets need refinishing?<select name="cabinetRefinishScope"><option>Entire kitchen</option><option>Kitchen + bathrooms</option><option>Touchup</option><option>Small Job</option></select></label></div>';if(state.project==='Commercial painting')html='<label>Whatâs the commercial space?<select name="scope"><option>Office</option><option>Retail storefront</option><option>Warehouse or industrial</option></select></label>';box.innerHTML=html;if(state.project==='Cabinet Project'){let t=q('.tsrb-cabinet-type'),follow=q('.tsrb-cabinet-refinish');const sync=()=>{follow.hidden=t.value!=='Cabinet Refinishing (Painting)';};t.addEventListener('change',sync);sync();}} 17q('.tsrb-answers').onclick=async e=>{const b=e.currentTarget;setBusy(b,true);try{const ids={project:'83f10b2f-b222-801e-79fe-3a22eaa6627a',interior:'da5bae4b-dc0d-78a7-6614-3a22eaa6627a',exterior:'ebf9e4fe-c22f-c589-5eec-3a22eaa6627a',cabinet:'1fe801a9-af23-6a17-6681-3a22eaa6627a',commercial:'b2f347f4-b279-9099-3be9-3a22eaa6627a',start:'0fc24917-43d9-2133-c51d-3a22eaa6627a',details:'0d7af16d-6932-6580-f5f6-3a22eaa6627a',cabinetRefinish:'2f3a7
17ab5-f22e-0cac-2b8e-3a22eaa6627a'};let a={[ids.project]:state.project,[ids.start]:val('start'),[ids.details]:val('details')||'No additional details provided.'};let map={'Interior painting':'interior','Exterior painting':'exterior','Cabinet Project':'cabinet','Commercial painting':'commercial'};a[ids[map[state.project]]]=val('scope');if(state.project==='Cabinet Project'&&val('scope')==='Cabinet Refinishing (Painting)')a[ids.cabinetRefinish]=val('cabinetRefinishScope');await call('answers',{requestId:state.requestId,dynamicAnswers:a});show(5);}catch(e){msg(e.message)}finally{setBusy(b,false)}}; 18qa('[name="pref"]').forEach(r=>r.onchange=()=>{q('.tsrb-date').hidden=r.value!=='date'||!r.checked;}); 19function renderSlots(slots){let preference=val('daypart')||'Flexible',filtered=slots.filter(s=>matchesDaypart(s,preference)),note='';if(!filtered.length&&slots.length){filtered=slots;note='<p class="tsrb-note">We donât have an exact '+preference.toLowerCase()+' match right now, so here are all available times.</p>';}if(!filtered.length){q('.tsrb-slots').innerHTML='<div class="tsrb-request-box"><p><strong>No online times are available right now.</strong></p><p>Your information is already saved. You can have Top Shelf contact you to schedule.</p><button type="button" class="tsrb-request">Have Top Shelf contact me</button></div>';bindRequest();return;}q('.tsrb-slots').innerHTML='<h3>Choose an available time</h3>'+note+filtered.slice(0,20).map(s=>`<button type="button" class="tsrb-slot" data-id="${s.id}">${formatDate(s.suggestedDate)} · ${formatTime(s.startTime)}â${formatTime(s.endTime)}</button>`).join('')+'<button type="button" class="tsrb-request-link tsrb-request">None of these times work for me</button>';qa('.tsrb-slot').forEach(b=>b.onclick=async()=>{setBusy(b,true,'Checkingâ¦');try{await call('preview',{requestId:state.requestId,slotId:b.dataset.id});state.slotId=b.dataset.id;state.slotText=b.dataset.oldText||b.textContent;q('.tsrb-summary').innerHTML=`<div class="tsrb-confirm-card"><div class="tsrb-check">â</div><p class="tsrb-confirm-label">Your selected estimate time</p><h3>${state.slotText}</h3><p>Your information and project details are already saved with Top Shelf.</p></div>`;show(6);}catch(e){msg(e.message)}finally{setBusy(b,false)}});bindRequest();} 20function bindRequest(){qa('.tsrb-request').forEach(b=>b.onclick=()=>{fireRequest();q('[data-step="5"]').innerHTML='<div class="tsrb-final"><div class="tsrb-check">â</div><h2>Your estimate request has been received.</h2><p>
20No problem â a member of the Top Shelf team will contact you to find a time that works.</p><p class="tsrb-small">Your contact information and project details are already saved.</p></div>';});} 21q('.tsrb-find').onclick=async e=>{const b=e.currentTarget;setBusy(b,true,'Finding timesâ¦');try{let pref=q('[name="pref"]:checked').value,date=pref==='date'?val('preferredDate'):(()=>{const d=new Date(),y=d.getFullYear(),m=String(d.getMonth()+1).padStart(2,'0'),day=String(d.getDate()).padStart(2,'0');return `${y}-${m}-${day}`;})();if(pref==='date'&&!date){msg('Please choose your preferred date.');return;}await call('schedule',{requestId:state.requestId,contactAddressId:state.contactAddressId,isAsap:pref==='asap'?'1':'',isFlexibleDate:pref==='asap'?'1':'',preferredDate:date});let d=await call('slots',{requestId:state.requestId});renderSlots(d.slots||[]);}catch(e){msg(e.message)}finally{setBusy(b,false)}}; 22q('.tsrb-confirm').onclick=async e=>{const b=e.currentTarget;setBusy(b,true,'Confirmingâ¦');try{let d=await call('confirm',{requestId:state.requestId,slotId:state.slotId});if(d.appointmentConfirmed===false)throw Error('We couldnât confirm that time, but your request is saved. Top Shelf can follow up with you.');q('[data-step="6"]').innerHTML='<div class="tsrb-final"><div class="tsrb-check">â</div><h2>Youâre all set!</h2><p>Your FREE estimate with Top Shelf has been scheduled.</p><div class="tsrb-appointment">'+state.slotText+'</div><p>Weâve sent you a confirmation with your appointment details. We look forward to meeting you!</p></div>';if(window.fbq)fbq('track','Schedule');if(window.dataLayer)window.dataLayer.push({event:'booking_confirmed',routemize_request_id:state.requestId});}catch(e){msg(e.message)}finally{setBusy(b,false)}}; 23})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.