')
]);
document.querySelector('header').innerHTML=h;
document.querySelector('footer').innerHTML=f;
attachHeaderHandlers();
attachFooterHandlers();
wireGlobalDelegates();
applySavedTheme();
}
function attachHeaderHandlers(){
document.querySelectorAll('[data-open="#loginModal"]').forEach(b=>b.addEventListener('click',()=>document.getElementById('loginModal').showModal()));
document.querySelectorAll('[data-open="#registerModal"]').forEach(b=>b.addEventListener('click',()=>document.getElementById('registerModal').showModal()));
document.querySelectorAll('[data-open="#themeModal"]').forEach(b=>b.addEventListener('click',()=>document.getElementById('themeModal').showModal()));
}
function attachFooterHandlers(){
const b=document.querySelector('#cookieBanner');
if(b&&!localStorage.getItem('cookieConsent')) b.classList.remove('hidden');
document.querySelector('#cookieAccept')?.addEventListener('click',()=>{
localStorage.setItem('cookieConsent',JSON.stringify({necessary:true,analytics:true,date:Date.now()}));
b?.classList.add('hidden');
});
document.querySelector('#cookiePrefs')?.addEventListener('click',()=>document.querySelector('#cookieModal')?.showModal());
}
function wireGlobalDelegates(){
document.body.addEventListener('click',e=>{
const closeSel=e.target.getAttribute('data-close');
if(closeSel){
const dlg=document.querySelector(closeSel);
if(dlg && typeof dlg.close==='function') dlg.close();
}
});
document.querySelectorAll('dialog').forEach(dlg=>{
dlg.addEventListener('click',e=>{
const rect=dlg.getBoundingClientRect();
const inDialog = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
if(!inDialog) dlg.close();
});
});
document.addEventListener('keydown',e=>{
if(e.key==='Escape'){
const openDlg=[...document.querySelectorAll('dialog')].find(d=>d.open);
openDlg?.close();
}
});
}
function systemPrefersDark(){
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
function setTheme(m){
const mode = m==='system' ? (systemPrefersDark()?'dark':'light') : m;
document.documentElement.classList.toggle('dark',mode==='dark');
localStorage.setItem('theme',m);
}
function applySavedTheme(){
const saved=localStorage.getItem('theme')||'light';
setTheme(saved);
const themeForm=document.querySelector('#themeModal form');
if(themeForm){
const inp=themeForm.querySelector('input[name="theme"][value="'+saved+'"]');
if(inp) inp.checked=true;
}
if(saved==='system'){
const mq=window.matchMedia('(prefers-color-scheme: dark)');
mq.onchange=()=>setTheme('system');
}
}
function formatCAD(n){return new Intl.NumberFormat('en-CA',{style:'currency',currency:'CAD',maximumFractionDigits:0}).format(n);}
function calcEstimate(d){
let base=0;
let label='Unknown';
if(d.service==='sweep'){ base=169; label='Standard Sweep'; }
if(d.service==='l1'){ base=99; label='Level 1 Inspection'; }
if(d.service==='l2'){ base=249; label='Level 2 Inspection'; }
const lines=[];
lines.push({name:label,price:base});
const floors = Math.max(1, parseInt(d.floors||1,10));
const floorAdj=(floors-1)*25;
if(floorAdj>0) lines.push({name:`Extra height (${floors} floors)`,price:floorAdj});
const months = Math.max(0, parseInt(d.months||0,10));
const soilAdj=Math.max(0,(months-12))*2;
if(soilAdj>0) lines.push({name:`Build-up since last clean (${months} mo)`,price:soilAdj});
const regionNorth = ['YT','NT','NU'].includes(d.region);
const regionEast = ['NL','NS','NB','PE','QC'].includes(d.region);
const regionAdj = regionNorth?45:(regionEast?15:0);
if(regionAdj>0) lines.push({name:`Regional travel/handling (${d.region})`,price:regionAdj});
if(d.home==='cottage'){ lines.push({name:'Cottage/cabin adjustments',price:20}); }
let addons=0;
if(d.addon_cap==='1'){ lines.push({name:'Stainless cap install',price:179}); addons+=179; }
if(d.addon_bird==='1'){ lines.push({name:'Bird nest removal',price:89}); addons+=89; }
if(d.addon_seal==='1'){ lines.push({name:'Minor crown seal',price:59}); addons+=59; }
const subtotal = lines.reduce((s,l)=>s+l.price,0);
return { total: Math.round(subtotal), lines };
}
function renderBreakdown(target, est){
target.innerHTML='';
est.lines.forEach(l=>{
const row=document.createElement('div');
row.className='flex items-center justify-between';
const name=document.createElement('span');
name.textContent=l.name;
const price=document.createElement('span');
price.textContent=formatCAD(l.price);
row.appendChild(name);
row.appendChild(price);
target.appendChild(row);
});
}
function saveEstimatorState(d){
localStorage.setItem('estimatorState',JSON.stringify(d));
}
function loadEstimatorState(){
try{ return JSON.parse(localStorage.getItem('estimatorState')||'{}'); }catch(e){ return {}; }
}
function hydrateForm(form, data){
[...form.elements].forEach(el=>{
if(!el.name) return;
if(el.type==='checkbox'){
el.checked = data[el.name]==='1';
}else{
if(data[el.name]!=null) el.value = data[el.name];
}
});
}
function collectForm(form){
const o = Object.fromEntries(new FormData(form).entries());
// ensure unchecked checkboxes are '0'
form.querySelectorAll('input[type="checkbox"]').forEach(cb=>{
if(!o[cb.name]) o[cb.name]='0';
});
return o;
}
document.addEventListener('DOMContentLoaded',()=>{
injectPartials();
const themeForm=document.querySelector('#themeModal form');
themeForm?.addEventListener('submit',e=>{
e.preventDefault();
const m=(new FormData(themeForm).get('theme')||'light');
setTheme(m);
document.getElementById('themeModal').close();
});
const form=document.getElementById('estForm');
const out=document.getElementById('estResult');
const brk=document.getElementById('estBreakdown');
const resetBtn=document.getElementById('resetBtn');
const saved=loadEstimatorState();
if(Object.keys(saved).length){
hydrateForm(form,saved);
}
function updateEst(){
const d=collectForm(form);
const ready = d.region && d.home && d.floors && d.months && d.service;
if(ready){
const est=calcEstimate(d);
out.textContent='Estimated total: '+formatCAD(est.total);
renderBreakdown(brk, est);
}else{
out.textContent='Estimated total: —';
brk.innerHTML='';
}
saveEstimatorState(d);
}
form.addEventListener('input',updateEst);
form.addEventListener('change',updateEst);
updateEst();
resetBtn.addEventListener('click',()=>{
form.reset();
localStorage.removeItem('estimatorState');
updateEst();
});
form.addEventListener('submit',e=>{
e.preventDefault();
const d=collectForm(form);
const est=calcEstimate(d);
document.getElementById('estimate_total').value=est.total;
document.getElementById('estimate_payload').value=JSON.stringify({inputs:d,estimate:est});
const summary=document.getElementById('quoteSummary');
summary.textContent = `Your estimate: ${formatCAD(est.total)} — ${d.service==='sweep'?'Standard Sweep':d.service==='l1'?'Level 1 Inspection':'Level 2 Inspection'} in ${d.region}, ${d.home}, ${d.floors} floors, last clean ${d.months} months ago.`;
document.getElementById('quoteModal').showModal();
});
document.getElementById('quoteForm')?.addEventListener('submit',e=>{
const tel=e.target.querySelector('input[name="phone"]');
const postal=e.target.querySelector('input[name="postal"]');
if(!tel.checkValidity() || !postal.checkValidity()){
e.preventDefault();
tel.reportValidity();
postal.reportValidity();
return;
}
});
});