`};
const tHtml=()=>{const q=(flt.tq||"").toLowerCase(),c=flt.tc||"";const r=S.tasks.filter(x=>(!q||x.t.toLowerCase().includes(q))&&(!c||x.cat===c));return tbl(["","Task","Category","Due","Priority",""],r.map(x=>[``,`${esc(x.t)}`,esc(x.cat),dt(x.due),`${x.pri}`,acts("tasks",x.id)]),"No tasks match. Add one to get going.")};
V.tasks=()=>{const td=S.tasks.filter(x=>x.done).length,tt=S.tasks.length;return head("Checklist",`${td} of ${tt} tasks done`,addBtn("tasks","Add task"))+`
${bar(tt?td/tt*100:0)}
${tHtml()}
`};
V.budget=()=>{const e=sum(S.exp,"e"),a=sum(S.exp,"a"),left=S.budget-a;return head("Budget",`${S.exp.filter(x=>x.paid==="Paid").length} of ${S.exp.length} items paid`,addBtn("exp","Add expense"))+`
${stat("Total budget",money(S.budget),"set in details")}${stat("Estimated",money(e),"planned costs")}${stat("Spent",money(a),"actual costs",S.budget?a/S.budget*100:null)}${stat(left<0?"Over budget":"Remaining",money(Math.abs(left)),S.budget?"of your budget":"set a budget first")}
Spending split
${donut()}
${tbl(["Item","Category","Estimated","Actual","Status",""],S.exp.map(x=>[esc(x.n),esc(x.cat),money(x.e),money(x.a),``,acts("exp",x.id)]),"No expenses yet. Add your first one.")}
`};
const gHtml=()=>{const q=(flt.gq||"").toLowerCase(),r=flt.gr||"";const l=S.guests.filter(g=>(!q||g.n.toLowerCase().includes(q))&&(!r||g.rsvp===r));return tbl(["Name","Contact","RSVP","Meal","Party","Table",""],l.map(g=>[`${esc(g.n)}${g.note?` ${esc(g.note)}`:""}`,esc(g.contact)||"\u2014",``,esc(g.meal),party(g),esc(g.table)||"\u2014",acts("guests",g.id)]),"No guests match. Add your first guest.")};
V.guests=()=>{const c=r=>S.guests.filter(g=>g.rsvp===r).reduce((s,g)=>s+party(g),0);return head("Guest list",`${S.guests.length} invitations`,addBtn("guests","Add guest"))+`
${stat("Invited",S.guests.reduce((s,g)=>s+party(g),0),"people in total")}${stat("Attending",c("Attending"),"confirmed")}${stat("Pending",c("Pending"),"awaiting reply")}${stat("Declined",c("Declined"),"can\u2019t make it")}
${gHtml()}
`};
V.vendors=()=>head("Vendors",`${S.vendors.length} on your list`,addBtn("vendors","Add vendor"))+(S.vendors.length?`
${S.vendors.map(v=>`
${esc(v.type)}${acts("vendors",v.id)}
${esc(v.n)}
${esc(v.contact)||"No contact yet"}
${v.price?money(v.price):"No quote"}
`).join("")}
`:`
No vendors yet. Add the first one you are considering.
`);
V.time=()=>head("Timeline","Milestones and the schedule for the big day",addBtn("events","Add event"))+`
`;
V.notes=()=>head("Notes","Ideas, inspiration and reminders. Saved as you type.")+``;
V.set=()=>head("Settings","Details, backups and data")+`
Wedding details
${esc(names())}${S.date?" \u00b7 "+dt(S.date):""}
Backup and restore
Your data is stored in this browser only. Download a backup file, or copy it as text, to keep it safe.
Data
`;
const side=()=>`
Wedding Planner${esc(names())}
`;
function render(){$("#wpside").innerHTML=side();$("#wpmain").innerHTML=V[view]()}
function form(c,id){const it=c==="set"?S:id?S[c].find(x=>x.id===id):Object.assign({},DEF[c]);$("#wpmodal").innerHTML=``;const i=$("#wpmodal input,#modal select");if(i)i.focus()}
function ask(m,fn){cb=fn;$("#wpmodal").innerHTML=`
${m}
`}
function close(){$("#wpmodal").innerHTML="";cb=null}
function sample(){const d=n=>new Date(Date.now()+n*864e5).toISOString().slice(0,10),o=def();Object.assign(o,{a:"Sophie",b:"Daniel",date:d(150),venue:"Willow Garden Estate",cur:S.cur,budget:20000,
exp:[["Venue","Venue",5000,5000,"Paid"],["Catering","Catering",4500,0,"Unpaid"],["Photographer","Photography",2200,500,"Unpaid"],["Florist","Flowers",1200,0,"Unpaid"],["Wedding dress","Attire",1800,1650,"Paid"]].map(r=>({id:uid(),n:r[0],cat:r[1],e:r[2],a:r[3],paid:r[4]})),
guests:[["Olivia Bennett","olivia@email.com","Attending","Meat",1,"1"],["Noah Carter","555-0142","Attending","Vegetarian",0,"1"],["Mia Foster","","Pending","No preference",1,""],["Liam Brooks","","Declined","No preference",0,""]].map(r=>({id:uid(),n:r[0],contact:r[1],rsvp:r[2],meal:r[3],plus:r[4],table:r[5],note:""})),
vendors:[["Willow Garden Estate","Venue","hello@willow.example",5000,"Paid in full"],["Bright Lens Studio","Photographer","555-0188",2200,"Booked"],["Petal & Stem","Florist","",1200,"Contacted"]].map(r=>({id:uid(),n:r[0],type:r[1],contact:r[2],price:r[3],st:r[4]})),
events:[["Rehearsal dinner",149,"18:00","The Garden Bistro"],["Hair and makeup",150,"09:00","Bridal suite"],["Ceremony",150,"15:00","Willow Garden"],["Reception",150,"17:30","Grand Ballroom"]].map(r=>({id:uid(),t:r[0],date:d(r[1]),time:r[2],loc:r[3],note:""}))});
o.tasks.forEach((t,i)=>{t.due=d(10+i*10);t.done=i<3?1:0});return o}
function imp(txt){try{S=Object.assign(def(),JSON.parse(txt));save();render();$("#msg").textContent="Backup restored."}catch(e){const m=$("#msg");if(m)m.textContent="That backup could not be read. Check you used the full text or file."}}
document.addEventListener("click",e=>{const b=e.target.closest("[data-a]");if(!b)return;const a=b.dataset.a.split(":"),k=a[0];
if(k==="nav"){view=a[1];render();scrollTo(0,0);return}
if(k==="add"){form(a[1]);return}
if(k==="edit"){form(a[1],a[2]);return}
if(k==="close"){close();return}
if(k==="del"){ask("Delete this item?",()=>{S[a[1]]=S[a[1]].filter(x=>x.id!==a[2]);save();render()});return}
if(k==="yes"){const f=cb;close();if(f)f();return}
if(k==="tog"){const t=S.tasks.find(x=>x.id===a[1]);t.done=t.done?0:1}
else if(k==="tgp"){const x=S.exp.find(y=>y.id===a[1]);x.paid=x.paid==="Paid"?"Unpaid":"Paid"}
else if(k==="rsvp"){const g=S.guests.find(y=>y.id===a[1]);g.rsvp={Pending:"Attending",Attending:"Declined",Declined:"Pending"}[g.rsvp]}
else if(k==="vst"){const v=S.vendors.find(y=>y.id===a[1]),o=["Researching","Contacted","Booked","Paid in full"];v.st=o[(o.indexOf(v.st)+1)%4]}
else if(k==="dl"){const u=URL.createObjectURL(new Blob([JSON.stringify(S,null,1)],{type:"application/json"})),x=document.createElement("a");x.href=u;x.download="wedding-planner-backup.json";document.body.appendChild(x);x.click();x.remove();setTimeout(()=>URL.revokeObjectURL(u),500);$("#msg").textContent="If nothing downloaded, use Show backup text and copy it instead.";return}
else if(k==="show"){$("#bk").value=JSON.stringify(S);$("#msg").textContent="Copy all the text above and keep it somewhere safe.";return}
else if(k==="rest"){imp($("#bk").value);return}
else if(k==="sample"){ask("Replace your current data with sample data?",()=>{S=sample();save();render()});return}
else if(k==="reset"){ask("Erase everything and start fresh?",()=>{S=def();save();render()});return}
else return;
save();render()});
document.addEventListener("submit",e=>{e.preventDefault();const f=e.target;if(!f.dataset.f)return;const p=f.dataset.f.split(":"),c=p[0],id=p[1],d=Object.fromEntries(new FormData(f));FLD[c].forEach(x=>{if(x.t==="number")d[x.k]=+d[x.k]||0;else if(typeof d[x.k]==="string")d[x.k]=d[x.k].trim()});
if(c==="set"){if(!d.cur)d.cur="$";Object.assign(S,d)}else if(id)Object.assign(S[c].find(x=>x.id===id),d);else S[c].push(Object.assign({id:uid()},d));
save();close();render()});
document.addEventListener("input",e=>{const t=e.target;if(t.id==="nt"){S.notes=t.value;save()}else if(t.id==="tq"||t.id==="gq"){flt[t.id]=t.value;$("#tb").innerHTML=(t.id==="tq"?tHtml:gHtml)()}});
document.addEventListener("change",e=>{const t=e.target;if(t.id==="tc"||t.id==="gr"){flt[t.id]=t.value;$("#tb").innerHTML=(t.id==="tc"?tHtml:gHtml)()}else if(t.id==="imp"&&t.files[0]){const r=new FileReader();r.onload=()=>imp(r.result);r.readAsText(t.files[0])}});
document.addEventListener("keydown",e=>{if(e.key==="Escape")close()});
setInterval(()=>{const e=$("#cd");if(e)e.innerHTML=cdh()},60000);
render();