'use strict';

// ============================================================
// SiteSight V1.99.8 — Professional Public Demo
// Final compatibility layer: scoped access, dependable field
// workflows, clearer commercial control and accessible phone UI.
// ============================================================

function v1996IsPhone(){
  if(!window.matchMedia)return false;
  return window.matchMedia('(max-width:720px), (orientation:landscape) and (max-height:600px) and (max-width:1024px)').matches
}
// Keep every inherited phone-only workflow aligned with the final device rule.
v1995IsMobile=v1996IsPhone;
function v1996Esc(value){
  return String(value??'').replace(/[&<>"']/g,char=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[char]))
}
function v1996PermittedSiteIds(user=currentUser(),includeArchived=false){
  if(!user)return [];
  let valid=new Set((db.sites||[]).filter(row=>includeArchived||!row.archived).map(row=>row.id));
  if(user.role==='Director'||user.allSitesAccess===true)return [...valid];
  return (user.allowedSites||[]).filter(id=>valid.has(id))
}
function v1996NormaliseSiteContext(){
  let ids=v1996PermittedSiteIds();
  if(ids.length===1){site=ids[0];if(typeof attendanceSite!=='undefined')attendanceSite=ids[0];return}
  if(site!=='all'&&!ids.includes(site))site='all';
  if(typeof attendanceSite!=='undefined'&&attendanceSite!=='all'&&!ids.includes(attendanceSite))attendanceSite='all'
}
function v1996SiteLabel(){return site==='all'?'All permitted Sites':siteName(site)}

const v1996BaseSetSiteContext=setSiteContext;
setSiteContext=function(value){
  let ids=v1996PermittedSiteIds();
  if(ids.length===1)value=ids[0];
  if(value!=='all'&&!ids.includes(value))return toast('That Site is outside your access');
  return v1996BaseSetSiteContext(value)
};

function v1996AllMembers(){return (db.gangs||[]).flatMap(group=>(group.members||[]).map(member=>({member,group})))}
function v1996MemberSiteIds(member){
  let ids=new Set([...(member?.clockSiteIds||[]),...(member?.siteIds||[])]),linked=(db.users||[]).find(user=>user.linkedMemberId===member?.id);
  for(const id of linked?.allowedSites||[])ids.add(id);
  for(const item of db.items||[]){
    let assigned=item.assignedId===member?.id||(item.priceworkAllocations||[]).some(row=>row.memberId===member?.id)||(item.lifts||[]).some(lift=>lift.assignedId===member?.id||(lift.priceworkAllocations||[]).some(row=>row.memberId===member?.id));
    if(assigned)ids.add(item.site)
  }
  for(const row of db.attendanceShifts||[])if(row.workerId===member?.id)ids.add(row.siteId);
  for(const row of db.dayworks||[])if([row.workerId,row.memberId,row.assignedId].includes(member?.id)&&row.siteId)ids.add(row.siteId);
  return [...ids].filter(Boolean)
}
function v1996CanViewMember(member,user=currentUser()){
  if(!member||!user)return false;
  if(user.role==='Director'||user.allSitesAccess===true)return true;
  let permitted=new Set(v1996PermittedSiteIds(user,true));
  return v1996MemberSiteIds(member).some(id=>permitted.has(id))
}
function v1996MemberMatchesContext(member,user=currentUser()){
  if(!v1996CanViewMember(member,user))return false;
  return site==='all'||v1996MemberSiteIds(member).includes(site)
}
function v1996MembersForSite(siteId){
  return v1996AllMembers().filter(row=>v1996CanViewMember(row.member)&&(siteId==='all'||v1996MemberSiteIds(row.member).includes(siteId)))
}
function v1996UsersForSite(siteId){
  return (db.users||[]).filter(user=>user.accountActive!==false&&(user.role==='Director'||user.allSitesAccess===true||(user.allowedSites||[]).includes(siteId)))
}
function v1996CanViewTeam(group){return !!group&&(group.members||[]).some(member=>v1996MemberMatchesContext(member))}

if(typeof v182MemberVisible==='function')v182MemberVisible=function(member){return v1996MemberMatchesContext(member)};
if(typeof v1995MemberVisible==='function')v1995MemberVisible=function(member){return v1996MemberMatchesContext(member)};

const v1996BaseOpenTeamMember=openTeamMember;
openTeamMember=function(teamId,memberId){
  let group=(db.gangs||[]).find(row=>row.id===teamId),member=group?.members?.find(row=>row.id===memberId);
  if(!member||!v1996CanViewMember(member))return toast('This person is outside your Site access');
  return v1996BaseOpenTeamMember(teamId,memberId)
};
const v1996BaseOpenGang=openGang;
openGang=function(id){let group=(db.gangs||[]).find(row=>row.id===id);if(!v1996CanViewTeam(group))return toast('This team is outside your Site access');return v1996BaseOpenGang(id)};
if(typeof gangDashboard==='function'){
  const v1996BaseGangDashboard=gangDashboard;
  gangDashboard=function(group){if(!group)return smartEmpty('Team unavailable.');return v1996BaseGangDashboard({...group,members:(group.members||[]).filter(member=>v1996MemberMatchesContext(member))})}
}
if(typeof v188AssignmentOptions==='function')v188AssignmentOptions=function(selected='',notRequired=false,siteId=site){
  let rows=v1996MembersForSite(siteId==='all'?(v1996PermittedSiteIds().length===1?v1996PermittedSiteIds()[0]:'all'):siteId).map(row=>row.member).sort((a,b)=>String(a.name).localeCompare(String(b.name)));
  return `<option value="">To be Confirmed</option><option value="__not_required__" ${notRequired?'selected':''}>Not Required — shared / changing labour</option>${rows.map(member=>`<option value="${v1996Esc(member.id)}" ${!notRequired&&selected===member.id?'selected':''}>${v1996Esc(member.name)}${member.role?' · '+v1996Esc(member.role):''}</option>`).join('')}`
};
issueAssigneeOptions=function(value='',siteId=''){
  let item=(db.items||[]).find(row=>row.id===selected),sid=siteId||item?.site||(site==='all'?v1996PermittedSiteIds()[0]:site),members=v1996MembersForSite(sid),users=v1996UsersForSite(sid),seen=new Set(),options=[['','Unassigned']];
  for(const row of members){if(seen.has('person:'+row.member.id))continue;seen.add('person:'+row.member.id);options.push(['person:'+row.member.id,'Person · '+row.member.name])}
  for(const user of users){if(seen.has('user:'+user.id))continue;seen.add('user:'+user.id);options.push(['user:'+user.id,'User · '+user.name])}
  return options.map(([id,label])=>`<option value="${v1996Esc(id)}" ${value===id?'selected':''}>${v1996Esc(label)}</option>`).join('')
};

const v1996BaseOpenItem=openItem;
openItem=function(id){let item=(db.items||[]).find(row=>row.id===id);if(!canViewItem(item))return toast('This work record is outside your access');return v1996BaseOpenItem(id)};
const v1996BaseViewIssue=viewIssue;
viewIssue=function(itemId,issueId){let item=(db.items||[]).find(row=>row.id===itemId),issue=item?.issues?.find(row=>row.id===issueId);if(!item||!issue||!canViewItem(item)||!issueRelevantToFieldUser(item,issue))return toast('This issue is outside your access');return v1996BaseViewIssue(itemId,issueId)};

function v1996GuardItemAction(name,mode='update'){
  let base=window[name];if(typeof base!=='function')return;
  window[name]=function(...args){let id=name==='resolveIssue'?args[0]:(args[0]&&String(args[0]).startsWith('v188-')?args[0]:selected),item=(db.items||[]).find(row=>row.id===id),allowed=mode==='view'?canViewItem(item):canUpdateItem(item);if(!allowed)return toast(mode==='view'?'This record is outside your access':'You cannot update this work record');return base.apply(this,args)}
}
['quickUpdate','quickProgress','saveQuickProgress','addWorkPhotos','saveWorkPhotos','saveLiftCard','signOff','saveWorkItemTeam','removeWorkPhoto'].forEach(name=>v1996GuardItemAction(name));
['addIssue','saveIssue'].forEach(name=>v1996GuardItemAction(name,'view'));
v1996GuardItemAction('resolveIssue','view');

const v1996BaseIssuesPage=issuesPage;
issuesPage=function(){
  let user=currentUser();if(!v1998OperatorHasWork(user))return v1996BaseIssuesPage();
  if(!hasPerm('view_issues'))return smartEmpty('Issue permission required.');
  if(!['relevant','urgent','mine','raised'].includes(issueFocusFilter))issueFocusFilter='relevant';
  let rows=(db.items||[]).filter(item=>canViewItem(item)&&(site==='all'||item.site===site)).flatMap(item=>(item.issues||[]).filter(issue=>issueRelevantToFieldUser(item,issue,user)).map(issue=>({...issue,item,ref:item.ref,type:item.type,siteLabel:siteName(item.site)}))),search=String(issueSearch||'').trim().toLowerCase();
  if(search)rows=rows.filter(issue=>[issue.ref,issue.title,issue.details,issue.notes,issueAssigneeLabel(issue.assignedTo),issue.priority].some(value=>String(value||'').toLowerCase().includes(search)));
  if(issueFocusFilter==='urgent')rows=rows.filter(issue=>issue.priority==='Urgent');
  if(issueFocusFilter==='mine')rows=rows.filter(issue=>fieldIssueAssigned(issue,user));
  if(issueFocusFilter==='raised')rows=rows.filter(issue=>issue.createdById===user.id||issue.createdBy===user.name);
  let open=rows.filter(issue=>issue.status==='Open'),resolved=rows.filter(issue=>issue.status==='Resolved');
  return `<div class="topbar"><div><span class="att-kicker">MY SITE ISSUES</span><h1>Issues</h1><div class="sub">Issues connected to your assigned work</div></div></div><div class="panel issue-control-panel"><div class="searchbox"><span aria-hidden="true">⌕</span><input value="${v1996Esc(issueSearch)}" placeholder="Search my issues…" oninput="issueSearch=this.value;render()"></div><div class="issue-focus">${[['relevant','Relevant'],['urgent','Urgent'],['mine','Assigned to me'],['raised','Raised by me']].map(([id,label])=>`<button class="${issueFocusFilter===id?'active':''}" onclick="issueFocusFilter='${id}';render()">${label}</button>`).join('')}</div></div><section class="panel v1996-issue-list"><div class="issue-section-title"><h2>Open</h2><span class="badge b-red">${open.length}</span></div>${open.map(issueRow).join('')||smartEmpty('No relevant open issues.','You are all clear in this view.','','','✓')}<div class="issue-section-title"><h2>Resolved</h2><span class="badge b-green">${resolved.length}</span></div>${resolved.map(issueRow).join('')||smartEmpty('No resolved issues in this view.')}</section>`
};

// ----- Role-aware universal search ---------------------------------
function v1996TextMatch(query,values){let needle=String(query||'').toLowerCase(),compact=needle.replace(/[\u00a3,\s]/g,'');return values.some(value=>{let text=String(value||'').toLowerCase();return text.includes(needle)||(compact.length>=2&&text.replace(/[\u00a3,\s]/g,'').includes(compact))})}
function v1996OpenSearchSite(id){
  if(!canAccessSite(id))return toast('This Site is outside your access');site=id;selected=null;selectedGang=null;modal=null;
  if(v1998OperatorHasWork(currentUser()))view='my-work';else if(currentUser()?.role==='Operator'){attendanceTab='today';view='attendance'}else view='dashboard';render()
}
function v1996OpenSearchPerson(memberId){let row=v1996AllMembers().find(entry=>entry.member.id===memberId);if(!row||!v1996CanViewMember(row.member))return toast('This person is outside your Site access');modal=null;openTeamMember(row.group.id,memberId)}
function v1996OpenPaymentResult(paymentId){
  let row=typeof v195AllPayments==='function'?v195AllPayments().find(entry=>entry.payment.id===paymentId):null;if(!row||!canAccessSite(row.item.site)||!hasPerm('view_financials'))return toast('This payment is unavailable');
  site=row.item.site;paymentFrom=row.payment.date||'';paymentTo=row.payment.date||'';paymentSearch=row.payment.paymentReference||row.item.ref;if(typeof v1992PaymentPage!=='undefined')v1992PaymentPage=1;v192CommercialTab='history';view='commercial';modal=null;render()
}
function v1996OpenVariationResult(itemId,variationId){
  let item=(db.items||[]).find(row=>row.id===itemId),variation=item?.variations?.find(row=>row.id===variationId);if(!item||!variation||!canViewItem(item)||!hasPerm('view_financials'))return toast('This variation is unavailable');
  modal=null;openItem(item.id);requestAnimationFrame(()=>{let card=document.querySelector(`[data-variation-id="${CSS.escape(variationId)}"]`);card?.scrollIntoView({block:'center'});card?.classList.add('v1996-highlight')})
}
function v1996OpenPayRunResult(runId){let run=(db.payRuns||[]).find(row=>row.id===runId);if(!run||!(run.siteIds||[]).every(canAccessSite)||!hasPerm('view_pay_runs'))return toast('This Pay Run is unavailable');modal=null;if(v1996IsPhone()&&['Director','Contracts Manager'].includes(currentUser()?.role)){v192CommercialTab='mobile-approvals';view='commercial';render()}else if(typeof v1993OpenRun==='function')v1993OpenRun(run.id)}
function v1996OpenArchivedSites(siteId=''){
  if(!roleCanAccessPlots())return toast('Archived plot summaries are available to Supervisors and above');if(!hasPerm('view_archives'))return toast('Archive permission is required');let rows=(db.sites||[]).filter(row=>row.archived&&canAccessSite(row.id)&&(siteId?row.id===siteId:true));
  modal=`<div class="modal"><div class="modalbox v1996-archive-sheet" role="dialog" aria-modal="true" aria-label="Archived Sites"><button class="v1991-modal-close" data-dialog-close onclick="modal=null;render()" aria-label="Close archived Sites">×</button><span class="att-kicker">READ-ONLY ARCHIVE</span><h2>Archived Sites</h2>${rows.map(row=>{let items=(db.items||[]).filter(item=>item.site===row.id),complete=items.filter(item=>pct(item)===100).length,open=items.reduce((sum,item)=>sum+issues(item),0);return `<article><h3>${v1996Esc(row.name)}</h3><p>${v1996Esc(row.address||row.client||'Completed Site')}</p><div><span><small>Plots / work</small><b>${items.length}</b></span><span><small>Complete</small><b>${complete}</b></span><span><small>Open issues</small><b>${open}</b></span></div></article>`}).join('')||smartEmpty('No archived Sites in your access.')}<button class="btn light" onclick="modal=null;render()">Close</button></div></div>`;render()
}

globalSearchResults=function(query){
  let q=String(query||'').trim().toLowerCase();if(q.length<2)return[];let user=currentUser(),out=[],today=iso(),weekFrom=attendanceWeekStartISO(new Date()),weekTo=v174DateAdd(weekFrom,6),operatorToday=q==='today',operatorWeek=q==='this week',operatorHelp=q==='clocking help',myPlots=q==='my plots',openIssues=q==='open issues',currentDrawings=q==='current drawings';
  if(user?.role==='Operator'&&!v1998OperatorHasWork(user)){
    let linked=user.linkedMemberId;
    (db.attendanceShifts||[]).filter(row=>{let date=attendanceDateOnly(row.clockIn?.time,row.siteId);return row.workerId===linked&&canAccessSite(row.siteId)&&((operatorToday&&date===today)||(operatorWeek&&date>=weekFrom&&date<=weekTo)||v1996TextMatch(q,[row.clockIn?.time,row.clockOut?.time,siteName(row.siteId)]))}).slice(0,8).forEach(row=>out.push({kind:'My shift',icon:'◷',title:new Date(row.clockIn.time).toLocaleDateString('en-GB'),meta:`${siteName(row.siteId)} · ${row.clockOut?.time?'Completed':'Clocked in'}`,action:`closeGlobalSearch();goNav('attendance')`}));
    activeSites().filter(row=>v1996TextMatch(q,[row.name,row.address])).slice(0,4).forEach(row=>out.push({kind:'Site',icon:'⌖',title:row.name,meta:'Your clocking Site',action:`closeGlobalSearch();v1996OpenSearchSite('${row.id}')`}));
    if(operatorHelp||v1996TextMatch(q,['help','clock','location','gps','offline']))out.push({kind:'Help',icon:'?',title:'Clocking help',meta:'Simple help for Clock In and Clock Out',action:'closeGlobalSearch();v1992OpenHelp()'});
    return out.slice(0,18)
  }
  if(hasPerm('work_items'))(db.items||[]).filter(item=>canViewItem(item)&&(myPlots||v1996TextMatch(q,[item.ref,item.type,item.houseType,siteName(item.site),gangName(item.gang)]))).slice(0,9).forEach(item=>out.push({kind:'Work Item',icon:'▣',title:item.ref,meta:`${item.type} · ${siteName(item.site)} · ${overall(item)}`,action:`closeGlobalSearch();openItem('${item.id}')`}));
  if(hasPerm('view_issues'))for(const item of db.items||[])if(canViewItem(item))for(const issue of item.issues||[])if(issueRelevantToFieldUser(item,issue,user)&&((openIssues&&issue.status==='Open')||v1996TextMatch(q,[issue.title,issue.details,issue.notes,item.ref,siteName(item.site)])))out.push({kind:'Issue',icon:'⚠',title:issue.title,meta:`${item.ref} · ${siteName(item.site)} · ${issue.status}`,action:`closeGlobalSearch();viewIssue('${item.id}','${issue.id}')`});
  activeSites().filter(row=>canAccessSite(row.id)&&v1996TextMatch(q,[row.name,row.address,row.client,row.code])).slice(0,5).forEach(row=>out.push({kind:'Site',icon:'⌖',title:row.name,meta:roleCanAccessPlots(user)?`${(db.items||[]).filter(item=>canViewItem(item)&&item.site===row.id).length} plots / work items`:'Permitted Site',action:`closeGlobalSearch();v1996OpenSearchSite('${row.id}')`}));
  if(hasPerm('gangs'))for(const row of v1996AllMembers())if(v1996MemberMatchesContext(row.member,user)&&v1996TextMatch(q,[row.member.name,row.member.role,row.group.name]))out.push({kind:'Person',icon:'♙',title:row.member.name,meta:`${row.member.role||'Site person'} · ${row.group.name}`,action:`closeGlobalSearch();v1996OpenSearchPerson('${row.member.id}')`});
  if(hasPerm('view_drawings'))(db.siteDrawings||[]).filter(row=>canAccessSite(row.siteId)&&((currentDrawings&&row.revisionStatus!=='Superseded')||(!currentDrawings&&v1996TextMatch(q,[row.name,row.houseType,row.notes,row.fileName,siteName(row.siteId)])))).slice(0,6).forEach(row=>out.push({kind:'Drawing',icon:'▤',title:row.name,meta:`${siteName(row.siteId)} · ${row.houseType||'Drawing'}${row.revision?` · Rev ${row.revision}`:''}`,action:`closeGlobalSearch();openSiteDrawing('${row.id}')`}));
  if(hasPerm('view_site_diary'))(db.siteDiary||[]).filter(row=>canAccessSite(row.siteId)&&v1996TextMatch(q,[row.date,row.people,row.teams,row.work,row.deliveries,row.delays,row.notes,siteName(row.siteId)])).slice(0,6).forEach(row=>out.push({kind:'Diary',icon:'✎',title:`${row.date} · ${siteName(row.siteId)}`,meta:(row.work||row.notes||row.delays||'Site Diary record').replace(/\s+/g,' ').slice(0,105),action:`closeGlobalSearch();v1995OpenSearchDiary('${row.id}')`}));
  if(hasPerm('view_calendar'))(db.calendarNotes||[]).filter(row=>canAccessSite(row.siteId)&&v1996TextMatch(q,[row.title,row.notes,row.category,row.date,siteName(row.siteId)])).slice(0,6).forEach(row=>out.push({kind:'Calendar',icon:'□',title:row.title||'Calendar entry',meta:`${row.date||''} · ${siteName(row.siteId)}`,action:`closeGlobalSearch();v1995OpenSearchCalendar('${row.id}')`}));
  if(typeof v191DocumentVisible==='function'&&v173CanOpenView('documents'))(db.documents||[]).filter(row=>row.type==='file'&&v191DocumentVisible(row)&&v1996TextMatch(q,[row.name,row.originalName,row.notes,siteName(row.siteId)])).slice(0,6).forEach(row=>out.push({kind:'Document',icon:'▥',title:row.name||row.originalName,meta:`${row.siteId?siteName(row.siteId):'Company'} · Revision ${Number(row.versionNumber||1)}`,action:`closeGlobalSearch();v1995OpenSearchDocument('${row.id}')`}));
  if(hasPerm('view_financials')&&typeof v195AllPayments==='function')v195AllPayments().filter(row=>canAccessSite(row.item.site)&&v1996TextMatch(q,[row.payment.paymentReference,row.payment.bankPaymentReference,row.payment.notes,row.item.ref,row.stage,siteName(row.item.site),paymentPayeeName({...row.payment,item:row.item}),row.payment.date,attendanceMoney(row.payment.amount||0)])).slice(0,7).forEach(row=>out.push({kind:'Payment',icon:'£',title:paymentPayeeName({...row.payment,item:row.item}),meta:`${row.item.ref} · ${attendanceMoney(row.payment.amount||0)} · ${row.payment.paymentReference||row.payment.bankPaymentReference||v1993PaymentState(row.payment)}`,action:`closeGlobalSearch();v1996OpenPaymentResult('${row.payment.id}')`}));
  if(hasPerm('view_financials'))for(const item of db.items||[])if(canAccessSite(item.site))for(const variation of item.variations||[])if(v1996TextMatch(q,[variation.name,variation.stage,item.ref,siteName(item.site),variation.approvalStatus,variation.value]))out.push({kind:'Variation',icon:'±',title:variation.name,meta:`${item.ref} · ${attendanceMoney(variation.value||0)} · ${variation.approvalStatus||'Pending Approval'}`,action:`closeGlobalSearch();v1996OpenVariationResult('${item.id}','${variation.id}')`});
  if(hasPerm('view_pay_runs'))(db.payRuns||[]).filter(run=>(run.siteIds||[]).every(canAccessSite)&&v1996TextMatch(q,[run.from,run.to,run.status,run.paymentReference,...(run.siteIds||[]).map(siteName)])).slice(0,6).forEach(run=>out.push({kind:'Pay Run',icon:'£',title:`${run.from} → ${run.to}`,meta:`${run.status} · ${run.paymentReference||'No bank reference'}`,action:`closeGlobalSearch();v1996OpenPayRunResult('${run.id}')`}));
  if(hasPerm('view_archives'))(db.sites||[]).filter(row=>row.archived&&canAccessSite(row.id)&&v1996TextMatch(q,[row.name,row.address,row.client])).slice(0,5).forEach(row=>out.push({kind:'Archived Site',icon:'▦',title:row.name,meta:'Read-only archived Site',action:`closeGlobalSearch();v1996OpenArchivedSites('${row.id}')`}));
  let seen=new Set(),unique=out.filter(row=>{let key=row.kind+'|'+row.action;if(seen.has(key))return false;seen.add(key);return true}),order=['Site','Work Item','Issue','Payment','Variation','Pay Run','Person','Diary','Drawing','Document','Calendar','Archived Site','My shift','Help'],balanced=[];
  for(const kind of order)balanced.push(...unique.filter(row=>row.kind===kind).slice(0,3));for(const row of unique)if(balanced.length<30&&!balanced.includes(row))balanced.push(row);return balanced.slice(0,30)
};

const V1996_SEARCH_LABELS={'Site':'Sites','Work Item':'Plots & work','Issue':'Issues','Payment':'Payments','Variation':'Variations','Pay Run':'Pay Runs','Person':'People','Diary':'Site Diary','Drawing':'Drawings','Document':'Documents','Calendar':'Calendar','Archived Site':'Archived Sites','My shift':'My shifts','Help':'Help'};
globalSearchResultsHtml=function(query){
  let q=String(query||'').trim(),rows=globalSearchResults(q),user=currentUser();
  if(q.length<2){let fieldOperator=v1998OperatorHasWork(user),copy=fieldOperator?'Search an assigned plot, relevant issue or drawing.':user?.role==='Operator'?'Search your shifts, clocking Site or help.':'Search a plot, Site, person, payment, issue or date.',examples=fieldOperator?['My plots','Open issues','Current drawings']:user?.role==='Operator'?['Today','This week','Clocking help']:['Plot','Payment','Today'];return `<div class="global-search-start"><span class="att-kicker">FIND IT FAST</span><b>${copy}</b><p>Results only include information you are allowed to view.</p><div class="v1995-search-examples">${examples.map(example=>`<button onclick="let field=document.getElementById('global-search-input');field.value='${example}';updateGlobalSearch('${example}')">${example}</button>`).join('')}</div></div>`}
  if(!rows.length)return smartEmpty('Nothing found','Try a shorter word, exact plot number, Site, person or reference.','','','⌕');
  return Object.keys(V1996_SEARCH_LABELS).map(kind=>{let group=rows.filter(row=>row.kind===kind);if(!group.length)return'';return `<section class="v1996-search-group" aria-labelledby="v1996-search-${kind.replace(/\s/g,'-')}"><h2 id="v1996-search-${kind.replace(/\s/g,'-')}" class="global-result-group">${V1996_SEARCH_LABELS[kind]}</h2>${group.map(row=>`<button class="global-result-row" onclick="${row.action}"><i aria-hidden="true">${row.icon}</i><span><b>${v1996Esc(row.title)}</b><small>${v1996Esc(row.meta)}</small></span><strong aria-hidden="true">›</strong></button>`).join('')}</section>`}).join('')
};
openGlobalSearch=function(){
  if(!v1996ConfirmDraftExit())return;globalSearchQuery='';
  let fieldOperator=v1998OperatorHasWork(currentUser()),placeholder=fieldOperator?'Plot, issue or drawing…':currentUser()?.role==='Operator'?'Date, shift or help…':'Plot, person, payment, Site…';modal=`<div class="modal global-search-modal" role="dialog" aria-modal="true" aria-label="Search SiteSight"><div class="global-search-box"><div class="global-search-head"><span aria-hidden="true">⌕</span><input id="global-search-input" autocomplete="off" enterkeyhint="search" aria-label="Search SiteSight" placeholder="${placeholder}" oninput="updateGlobalSearch(this.value)" onkeydown="if(event.key==='Escape')closeGlobalSearch()"><button type="button" data-dialog-close onclick="closeGlobalSearch()" aria-label="Close search">×</button></div><p id="global-search-status" class="sr-only" role="status" aria-live="polite"></p><div id="global-search-results" class="global-search-results">${globalSearchResultsHtml('')}</div><div class="global-search-foot"><span>Only information you are allowed to view is searched.</span></div></div></div>`;render();requestAnimationFrame(()=>document.getElementById('global-search-input')?.focus())
};
const v1996BaseUpdateGlobalSearch=updateGlobalSearch;
updateGlobalSearch=function(value){let result=v1996BaseUpdateGlobalSearch(value),count=globalSearchResults(value).length,status=document.getElementById('global-search-status');if(status)status.textContent=value.trim().length<2?'Type at least two characters':count?`${count} result${count===1?'':'s'}`:'No results';return result};

// ----- Notification validation and safe launch links ----------------
function v1996NotificationTarget(notification){return typeof v1994ResolvedNotificationTarget==='function'?v1994ResolvedNotificationTarget(notification):{targetType:notification?.targetType||'',targetId:notification?.targetId||notification?.itemId||'',targetParentId:notification?.targetParentId||''}}
function v1996UserCanOpenNotification(notification,target,user=currentUser()){
  if(!notification||!target||!user)return false;if(notification.targetUserId&&notification.targetUserId!==user.id)return false;
  if(target.targetType==='payment'){let row=typeof v1996cEntryByPaymentId==='function'?v1996cEntryByPaymentId(target.targetId):v176ActualPayment(target.targetId);return !!row&&userHasPerm(user,'view_financials')&&userCanAccessSite(user,row.item.site)}
  if(target.targetType==='work'){let item=(db.items||[]).find(row=>row.id===target.targetId);return userHasPerm(user,'work_items')&&canViewItem(item,user)}
  if(target.targetType==='issue'){let item=(db.items||[]).find(row=>row.id===target.targetParentId),issue=item?.issues?.find(row=>row.id===target.targetId);return !!issue&&userHasPerm(user,'view_issues')&&canViewItem(item,user)&&issueRelevantToFieldUser(item,issue,user)}
  if(target.targetType==='calendar'){let row=(db.calendarNotes||[]).find(entry=>entry.id===target.targetId);return !!row&&userHasPerm(user,'view_calendar')&&userCanAccessSite(user,row.siteId)}
  if(target.targetType==='diary'){let row=(db.siteDiary||[]).find(entry=>entry.id===target.targetId);return !!row&&userHasPerm(user,'view_site_diary')&&userCanAccessSite(user,row.siteId)}
  if(target.targetType==='document'){let row=(db.documents||[]).find(entry=>entry.id===target.targetId);return !!row&&typeof v191DocumentVisible==='function'&&v191DocumentVisible(row,user)}
  if(target.targetType==='shift'){
    let row=(db.attendanceShifts||[]).find(entry=>entry.id===(target.targetId||notification.itemId));
    if(row)return userCanAccessSite(user,row.siteId)&&(row.workerId===user.linkedMemberId||userHasPerm(user,'view_attendance')||userHasPerm(user,'manage_attendance'));
    return ['attendance','clock-reminder'].includes(notification.kind)&&!!notification.siteId&&userCanAccessSite(user,notification.siteId)&&(userHasPerm(user,'clock_attendance')||userHasPerm(user,'view_attendance')||userHasPerm(user,'manage_attendance'))
  }
  if(target.targetType==='site'){return userHasPerm(user,'dashboard')&&userCanAccessSite(user,target.targetId||notification.siteId)}
  return !!notification.siteId&&userCanAccessSite(user,notification.siteId)
}
const v1996BaseEventVisible=typeof eventVisibleToUser==='function'?eventVisibleToUser:null;
if(v1996BaseEventVisible)eventVisibleToUser=function(event,user=currentUser()){if(event?.targetUserId&&event.targetUserId!==user?.id)return false;if(!v1996BaseEventVisible(event,user))return false;return v1996UserCanOpenNotification(event,v1996NotificationTarget(event),user)};
const v1996BaseOpenNotification=v199OpenNotification;
v199OpenNotification=function(id){let notification=(db.notifications||[]).find(row=>row.id===id),target=notification&&v1996NotificationTarget(notification);if(!notification||!v1996UserCanOpenNotification(notification,target))return toast('This notification is unavailable or outside your access');return v1996BaseOpenNotification(id)};

let v1996PendingLaunchTarget='';
function v1996CaptureLaunchTarget(value=location.href){
  try{let url=new URL(value,location.origin),notification=url.searchParams.get('notification'),target=notification?`?notification=${encodeURIComponent(notification)}`:url.searchParams.get('view')==='attendance'?'?view=attendance':'';if(target){v1996PendingLaunchTarget=target;sessionStorage.setItem('sitesight-v1996-launch',target)}return target}catch(error){return''}
}
function v1996ApplyPendingLaunchTarget(){
  if(!currentUser())return;let target=v1996PendingLaunchTarget;try{target=target||sessionStorage.getItem('sitesight-v1996-launch')||''}catch(error){}if(!target)return;
  try{let params=new URLSearchParams(target),notification=params.get('notification');if(notification)v199OpenNotification(notification);else if(params.get('view')==='attendance'&&v173CanOpenView('attendance')){attendanceTab='today';view='attendance';selected=null;modal=null;render()}sessionStorage.removeItem('sitesight-v1996-launch')}catch(error){}v1996PendingLaunchTarget=''
}
v1996CaptureLaunchTarget();
if(navigator.serviceWorker)navigator.serviceWorker.addEventListener('message',event=>{if(event.data?.type==='SITESIGHT_OPEN_TARGET'){v1996CaptureLaunchTarget(event.data.url||'');v1996ApplyPendingLaunchTarget()}});

// ----- One dependable draft/save guard for field forms --------------
let v1996DraftState={key:'',dirty:false,saving:false},v1996DiaryDraftTimer=0;
function v1996BeginDraft(key='field-editor'){v1996DraftState={key,dirty:false,saving:false};if(typeof clearDirty==='function')clearDirty()}
function v1996DraftChanged(){if(v1996DraftState.saving)return;v1996DraftState.dirty=true;if(typeof markDirty==='function')markDirty()}
function v1996CommitDraft(){v1996DraftState={key:'',dirty:false,saving:false};if(typeof clearDirty==='function')clearDirty()}
function v1996ConfirmDraftExit(){
  if(!v1996DraftState.dirty&&!(typeof dirty!=='undefined'&&dirty))return true;
  if(v1996DraftState.saving){toast('Please wait for this save to finish');return false}
  if(!confirm('Leave without saving your changes?'))return false;v1996CommitDraft();return true
}
function v1996CancelFieldEditor(){if(!v1996ConfirmDraftExit())return;modal=null;render()}
function v1996EnhanceFieldEditor(){
  let box=document.querySelector('.modalbox:not(.global-search-box):not(.v1995-account-menu):not(.v199-notifications):not(.v1996-readonly-sheet)');if(!box||box.dataset.v1996DraftBound)return;
  let editable=box.querySelector('textarea,input:not([type="hidden"]):not([type="button"]):not([type="submit"]),select,[contenteditable="true"]');if(!editable)return;box.dataset.v1996DraftBound='1';box.classList.add('field-mobile-editor');v1996BeginDraft(box.querySelector('h2')?.textContent||'field-editor');box.addEventListener('input',v1996DraftChanged);box.addEventListener('change',v1996DraftChanged);
  let actions=[...box.querySelectorAll('.actions,div')].filter(row=>row.querySelector(':scope > button')&&/save|upload|add|confirm|submit|approve|record/i.test(row.textContent||'')).at(-1);if(actions)actions.classList.add('field-mobile-actions')
}
function v1996GuardSaveTap(event){
  let button=event.target.closest('.field-mobile-editor button');if(!button||button.disabled||!/save|upload|add issue|record|confirm/i.test(button.textContent||''))return;button.dataset.v1996Original=button.textContent;setTimeout(()=>{if(document.body.contains(button)&&button.disabled&&v1996DraftState.saving===false){button.disabled=false;button.textContent=button.dataset.v1996Original||'Save'}},5000)
}
document.addEventListener('click',v1996GuardSaveTap,true);
const v1996BaseGoNav=goNav;
goNav=function(target){if(target==='clock')target='attendance';if(target==='dayworks'){type='Daywork';status='all';selected=null;target=v1998OperatorHasWork(currentUser())?'my-work':'work-items'}if(!v1996ConfirmDraftExit())return;return v1996BaseGoNav(target)};
if(typeof v1994GoBack==='function')v1994GoBack=function(){if(!v1996ConfirmDraftExit()||!v1994History.length)return;let state=v1994History.pop();v1994Restoring=true;modal=null;settingsOpen=false;v1994RestoreState(state);render();v1994Restoring=false;requestAnimationFrame(()=>window.scrollTo(0,Number(state.scrollY||0)))};
if(typeof v1995OpenMobileAccount==='function'){
  const v1996BaseOpenAccount=v1995OpenMobileAccount;
  v1995OpenMobileAccount=function(){if(!v1996ConfirmDraftExit())return;return v1996BaseOpenAccount()}
}
if(typeof v1991CloseModal==='function'){
  const v1996BaseCloseModal=v1991CloseModal;
  v1991CloseModal=function(){if(!v1996ConfirmDraftExit())return;return v1996BaseCloseModal()}
}

function v1996DiaryDraftKey(siteId,date){return `sitesight-diary-draft:${currentUserId}:${siteId}:${date}`}
function v1996DiaryDraftValues(){let siteId=document.getElementById('sd-site')?.value||'',date=document.getElementById('sd-date')?.value||'';return {siteId,date,teams:document.getElementById('sd-teams')?.value||'',work:document.getElementById('sd-work')?.value||'',deliveries:document.getElementById('sd-del')?.value||'',delays:document.getElementById('sd-delay')?.value||'',notes:document.getElementById('sd-notes')?.value||'',savedAt:new Date().toISOString()}}
function v1996SaveDiaryDraft(){let draft=v1996DiaryDraftValues();if(!draft.siteId||!draft.date)return;try{localStorage.setItem(v1996DiaryDraftKey(draft.siteId,draft.date),JSON.stringify(draft))}catch(error){}}
function v1996LoadDiaryDraft(siteId,date){try{return JSON.parse(localStorage.getItem(v1996DiaryDraftKey(siteId,date))||'null')}catch(error){return null}}
function v1996ClearDiaryDraft(siteId,date){try{localStorage.removeItem(v1996DiaryDraftKey(siteId,date))}catch(error){}}
if(typeof v1995DiaryChanged==='function'){
  const v1996BaseDiaryChanged=v1995DiaryChanged;
  v1995DiaryChanged=function(id){let result=v1996BaseDiaryChanged(id);clearTimeout(v1996DiaryDraftTimer);v1996DiaryDraftTimer=setTimeout(v1996SaveDiaryDraft,500);return result}
}
function v1996OpenDiaryDuplicate(id){let row=(db.siteDiary||[]).find(entry=>entry.id===id);if(!row)return;modal=null;site=row.siteId;view='diary';render();setTimeout(()=>openDiaryEntry(id),30)}
function v1996ShowDiaryDuplicate(row){
  modal=`<div class="modal"><div class="modalbox v1996-duplicate-diary" role="dialog" aria-modal="true"><span class="att-kicker">ONE DAILY RECORD</span><h2>A Site Diary already exists</h2><p>${v1996Esc(siteName(row.siteId))} already has an original diary for <b>${v1996Esc(row.date)}</b>. Open it or add a dated amendment so the record stays clear.</p><div class="field-mobile-actions"><button class="btn light" onclick="v1996OpenDiaryDuplicate('${row.id}')">Open existing</button><button class="btn orange" onclick="v1996OpenDiaryDuplicate('${row.id}')">Add amendment</button></div></div></div>`;render()
}
saveDiaryEntry=async function(){
  if(!hasPerm('manage_site_diary'))return toast('You do not have permission to manage the Site Diary');let draft=v1996DiaryDraftValues(),button=document.getElementById('v1995-save-diary'),files=uploadFiles('sd-photos');
  if(!draft.date)return toast('Choose the diary date');if(!draft.siteId)return toast('Choose the Site');if(![draft.teams,draft.work,draft.deliveries,draft.delays,draft.notes].some(value=>value.trim())&&!files.length)return toast('Add at least one diary detail or photo');
  let existing=(db.siteDiary||[]).find(row=>row.siteId===draft.siteId&&row.date===draft.date);if(existing){v1996SaveDiaryDraft();if(button){button.disabled=false;button.textContent='✓ Save Site Diary'}return v1996ShowDiaryDuplicate(existing)}
  if(v1996DraftState.saving)return;v1996DraftState.saving=true;if(button){button.disabled=true;button.textContent='Saving Site Diary…'}
  let entry={id:'sd'+Date.now(),date:draft.date,siteId:draft.siteId,teams:draft.teams,work:draft.work,deliveries:draft.deliveries,delays:draft.delays,notes:draft.notes,photos:[],amendments:[],by:currentUser()?.name||'',createdAt:new Date().toISOString()};
  try{if(files.length)await new Promise(resolve=>savePhotosToArray(files,entry.photos,resolve,'sd-photos'));db.siteDiary.unshift(entry);save();audit('diary','site diary',entry.id,`Site Diary entry added for ${entry.date}`,entry.siteId);emitNotification('site',entry.siteId,`Site diary added for ${entry.date}`,entry.id);clearCameraFiles('sd-photos');v1996ClearDiaryDraft(entry.siteId,entry.date);v1996CommitDraft();modal=null;render();savedState('Site Diary saved')}
  catch(error){v1996DraftState.saving=false;if(button){button.disabled=false;button.textContent='✓ Save Site Diary'}toast('The Site Diary could not be saved. Your text remains here.')}
};

// Drawings use shared media whenever a shared deployment is available.
async function v1996StoreDrawingFile(file,siteId){
  if(sharedMode&&navigator.onLine)return uploadBlobToServer(file,file.name||'drawing',siteId);
  if(sharedMode){let key=`drawing-${Date.now()}-${crypto.randomUUID?.()||Math.random().toString(16).slice(2)}`;await putPendingBlob(key,file);return `pending:${key}`}
  return storeDrawingInBrowser(file)
}
saveSiteDrawing=async function(){
  if(!hasPerm('manage_drawings'))return toast('You do not have drawing upload access');let file=uploadFiles('sdw-file')[0],siteId=document.getElementById('sdw-site')?.value||'',houseType=(document.getElementById('sdw-house')?.value||'').trim(),name=(document.getElementById('sdw-name')?.value||'').trim(),notes=(document.getElementById('sdw-notes')?.value||'').trim(),drawingNumber=(document.getElementById('sdw-number')?.value||'').trim(),revision=(document.getElementById('sdw-revision')?.value||'').trim(),issuedDate=document.getElementById('sdw-issued')?.value||iso();if(!file||!siteId||!houseType||!name||!drawingNumber||!revision)return toast('Site, house type, drawing number, revision, drawing name and file are required');if(!canAccessSite(siteId))return toast('That Site is outside your access');
  let previous=(db.siteDrawings||[]).filter(row=>row.siteId===siteId&&String(row.drawingNumber||'').trim().toLowerCase()===drawingNumber.toLowerCase()&&row.revisionStatus!=='Superseded');if(previous.length&&!confirm(`A current ${drawingNumber} drawing already exists. Upload Revision ${revision} and mark the previous revision SUPERSEDED?`))return toast('Drawing upload cancelled');
  try{let data=await v1996StoreDrawingFile(file,siteId),drawing={id:'sdw'+Date.now(),siteId,houseType,name,notes,drawingNumber,revision,issuedDate,revisionStatus:'Current',supersededBy:'',supersededAt:'',supersededByUser:'',fileName:file.name||'Camera drawing.jpg',type:file.type||'image/jpeg',data,uploadedDate:iso(),uploadedAt:new Date().toISOString(),uploadedBy:currentUser()?.name||'',lastUsedAt:Date.now(),storage:String(data).startsWith('browser:')?'browser':String(data).startsWith('pending:')?'pending':'shared',takeoffs:[],takeoffCalibration:null,highlights:[]};previous.forEach(row=>{row.revisionStatus='Superseded';row.supersededBy=drawing.id;row.supersededAt=new Date().toISOString();row.supersededByUser=currentUser()?.name||''});db.siteDrawings.push(drawing);clearCameraFiles('sdw-file');save();audit('drawing','drawing library',drawing.id,`${drawingNumber}: Revision ${revision} uploaded CURRENT${previous.length?` · ${previous.length} previous revision${previous.length===1?'':'s'} superseded`:''}`,siteId);v1996CommitDraft();modal=null;render();savedState(String(data).startsWith('pending:')?'Drawing saved on this phone — waiting to sync':sharedMode?'Drawing saved and shared':'Drawing saved on this test device')}catch(error){toast(error.message||'Drawing could not be saved')}
};
saveDrawing=async function(){
  if(!hasPerm('manage_drawings'))return toast('You do not have drawing upload access');let item=(db.items||[]).find(row=>row.id===selected),file=uploadFiles('df')[0],notes=document.getElementById('dn')?.value||'',displayName=(document.getElementById('ddn')?.value||'').trim(),drawingNumber=(document.getElementById('pd-number')?.value||'').trim(),revision=(document.getElementById('pd-revision')?.value||'').trim(),issuedDate=document.getElementById('pd-issued')?.value||iso();if(!item||!canViewItem(item))return toast('This work record is unavailable');if(!file||!drawingNumber||!revision)return toast('Choose a file and enter drawing number and revision');
  let previous=(item.drawings||[]).filter(row=>String(row.drawingNumber||'').trim().toLowerCase()===drawingNumber.toLowerCase()&&row.revisionStatus!=='Superseded');
  try{let data=await v1996StoreDrawingFile(file,item.site),drawing={id:'d'+Date.now(),name:displayName||file.name||'Camera drawing',originalName:file.name||'Camera drawing.jpg',notes,type:file.type||'image/jpeg',data,drawingNumber,revision,issuedDate,revisionStatus:'Current',supersededBy:'',uploadedDate:iso(),uploadedAt:new Date().toISOString(),uploadedBy:currentUser()?.name||'',storage:String(data).startsWith('browser:')?'browser':String(data).startsWith('pending:')?'pending':'shared',takeoffs:[],takeoffCalibration:null,highlights:[]};previous.forEach(row=>{row.revisionStatus='Superseded';row.supersededBy=drawing.id;row.supersededAt=new Date().toISOString();row.supersededByUser=currentUser()?.name||''});item.drawings.push(drawing);touch(item);clearCameraFiles('df');save();audit('drawing','work item',item.id,`${item.ref}: ${drawingNumber} Rev ${revision} uploaded CURRENT${previous.length?' · old revision superseded':''}`,item.site);emitNotification('drawing',item.site,`${item.ref}: drawing recorded — ${drawingNumber} Rev ${revision}`,item.id);v1996CommitDraft();modal=null;render();savedState(String(data).startsWith('pending:')?'Drawing saved on this phone — waiting to sync':sharedMode?'Drawing saved and shared':'Drawing saved on this test device')}catch(error){toast(error.message||'Drawing could not be saved')}
};

// ----- One canonical commercial calculation ------------------------
function v1996RoundMoney(value){return Math.round((Number(value)||0)*100)/100}
function v1996PaymentAmounts(payment={},overrides={}){
  let value={...payment,...overrides},retentionRelease=Math.max(0,Number(value.retentionRelease||0)),retentionHeld=retentionRelease?0:Math.max(0,Number(value.retentionHeld||0)),gross=Math.max(0,Number(value.grossAmount??(Number(value.amount||0)+retentionHeld+Number(value.cisDeduction||0))));if(retentionRelease)gross=Math.max(gross,retentionRelease);
  let cisGrossExVat=Math.max(0,Math.min(gross,Number(value.cisGrossExVat??Math.max(0,gross-retentionHeld)))),cisMaterials=Math.max(0,Math.min(cisGrossExVat,Number(value.cisMaterials||0))),cisLiable=Math.max(0,cisGrossExVat-cisMaterials),cisRate=Math.max(0,Number(value.cisDeductionRate??value.cisRate??0)),cisDeduction=value.cisRegistered===false?0:v1996RoundMoney(cisLiable*cisRate/100),net=v1996RoundMoney(Math.max(0,gross-retentionHeld-cisDeduction));
  return {gross:v1996RoundMoney(gross),retentionHeld:v1996RoundMoney(retentionHeld),retentionRelease:v1996RoundMoney(retentionRelease),cisGrossExVat:v1996RoundMoney(cisGrossExVat),cisMaterials:v1996RoundMoney(cisMaterials),cisLiable:v1996RoundMoney(cisLiable),cisRate,cisDeduction:v1996RoundMoney(cisDeduction),net}
}
v191PaymentGross=function(payment){return v1996PaymentAmounts(payment).gross};
v191PaymentSettled=function(payment){let amounts=v1996PaymentAmounts(payment);return amounts.gross-amounts.retentionHeld};
v179PaymentCalculation=function(payment,profile=typeof v177CisProfile==='function'?v177CisProfile(paymentPayeeKey(payment)):null){let rate=Number(payment.cisDeductionRate??(profile?Number(v177CisRate(profile)):0)),amounts=v1996PaymentAmounts(payment,{cisDeductionRate:rate});return {...amounts,invoiceTotal:amounts.gross,vat:Math.max(0,amounts.gross-amounts.cisGrossExVat),rate,liable:amounts.cisLiable,deduction:amounts.cisDeduction,materials:amounts.cisMaterials}}
v179UpdateCisPreview=function(){let gross=Number(document.getElementById('v179-recon-total')?.dataset.total||document.getElementById('v179-recon-gross')?.value||0),cisGrossExVat=Number(document.getElementById('v179-recon-gross')?.value||gross),cisMaterials=Number(document.getElementById('v177-recon-materials')?.value||0),cisDeductionRate=Number(document.getElementById('v177-recon-rate')?.value||0),amounts=v1996PaymentAmounts({grossAmount:gross,cisGrossExVat,cisMaterials,cisDeductionRate,cisRegistered:cisDeductionRate>0});[['vat',Math.max(0,gross-amounts.cisGrossExVat)],['materials',amounts.cisMaterials],['liable',amounts.cisLiable],['deduction',amounts.cisDeduction],['net',amounts.net]].forEach(([id,value])=>{let element=document.getElementById(`v179-preview-${id}`);if(element)element.textContent=attendanceMoney(value)})}
v177OpenPaymentReconciliation=function(paymentId){
  if(!(hasPerm('manage_payments')||hasPerm('lock_payroll')))return toast('Payment management permission is required');let found=v176ActualPayment(paymentId);if(!found)return;let payment=found.payment,key=paymentPayeeKey({...payment,item:found.item}),name=paymentPayeeName({...payment,item:found.item}),profile=v177CisProfile(key),amounts=v1996PaymentAmounts(payment,{cisDeductionRate:Number(payment.cisDeductionRate??v177CisRate(profile))});
  modal=`<div class="modal"><div class="modalbox"><span class="att-kicker">PAYMENT RECONCILIATION</span><h2>${v1996Esc(name)}</h2><p class="muted">${v1996Esc(found.item.ref)} · ${payment.date||'No date'} · gross ${attendanceMoney(amounts.gross)}</p><div id="v179-recon-total" data-total="${amounts.gross}"></div><div class="two"><div class="field"><label>CIS gross excluding VAT</label><input id="v179-recon-gross" class="input" type="number" min="0" max="${amounts.gross}" step="0.01" value="${amounts.cisGrossExVat.toFixed(2)}" oninput="v179UpdateCisPreview()"></div><div class="field"><label>Direct materials evidenced</label><input id="v177-recon-materials" class="input" type="number" min="0" max="${amounts.cisGrossExVat}" step="0.01" value="${amounts.cisMaterials.toFixed(2)}" oninput="v179UpdateCisPreview()"></div></div><div class="two"><div class="field"><label>CIS rate used</label><select id="v177-recon-rate" class="select" onchange="v179UpdateCisPreview()">${[0,20,30].map(rate=>`<option value="${rate}" ${amounts.cisRate===rate?'selected':''}>${rate}%</option>`).join('')}</select></div><div class="field"><label>Reconciliation status</label><select id="v177-recon-status" class="select">${['Unreconciled','Matched','Checked','Query'].map(status=>`<option ${payment.reconciliationStatus===status?'selected':''}>${status}</option>`).join('')}</select></div></div><div class="field"><label>Reference / accountant note</label><textarea id="v177-recon-note" class="textarea">${v1996Esc(payment.reconciliationNote||'')}</textarea></div><div class="v177-cis-calc"><span>VAT / outside CIS <b id="v179-preview-vat"></b></span><span>Materials <b id="v179-preview-materials"></b></span><span>CIS liable <b id="v179-preview-liable"></b></span><span>CIS retained <b id="v179-preview-deduction"></b></span><span>Net payment <b id="v179-preview-net"></b></span></div><div class="field-mobile-actions"><button class="btn light" onclick="modal=null;render()">Cancel</button><button class="btn orange" onclick="v177SavePaymentReconciliation('${paymentId}')">Save reconciliation</button></div></div></div>`;render();v179UpdateCisPreview()
};
v177SavePaymentReconciliation=function(paymentId){
  let found=v176ActualPayment(paymentId);if(!found)return;let payment=found.payment,gross=v191PaymentGross(payment),cisGrossExVat=Number(document.getElementById('v179-recon-gross')?.value||gross),cisMaterials=Number(document.getElementById('v177-recon-materials')?.value||0),cisDeductionRate=Number(document.getElementById('v177-recon-rate')?.value||0),status=document.getElementById('v177-recon-status')?.value||'Unreconciled',note=(document.getElementById('v177-recon-note')?.value||'').trim();if(cisGrossExVat<0||cisGrossExVat>gross)return toast('CIS gross must be between £0 and the gross payment');if(cisMaterials<0||cisMaterials>cisGrossExVat)return toast('Materials must be between £0 and the CIS gross');let amounts=v1996PaymentAmounts(payment,{grossAmount:gross,cisGrossExVat,cisMaterials,cisDeductionRate,cisRegistered:cisDeductionRate>0});Object.assign(payment,{grossAmount:amounts.gross,retentionHeld:amounts.retentionHeld,cisGrossExVat:amounts.cisGrossExVat,cisMaterials:amounts.cisMaterials,cisDeductionRate:amounts.cisRate,cisLiable:amounts.cisLiable,cisDeduction:amounts.cisDeduction,cisNet:amounts.net,amount:amounts.net,reconciliationStatus:status,reconciliationNote:note,reconciledAt:new Date().toISOString(),reconciledBy:currentUser()?.name||''});save();audit('payments','CIS reconciliation',paymentId,`${found.item.ref}: ${status.toLowerCase()} · CIS ${amounts.cisRate}% · net ${attendanceMoney(amounts.net)}`,found.item.site);v1996CommitDraft();modal=null;savedState('Payment reconciliation saved');render()
};

function v1996TargetPaymentTotals(target){
  let records=target?.payments||[],setPrice=Math.max(0,Number(target?.price||0)),standard=records.filter(row=>!row.amended&&!Number(row.retentionRelease||0)),paid=standard.filter(row=>(row.paymentState||'Paid')==='Paid'),ready=standard.filter(row=>row.paymentState==='Ready');return {setPrice,grossRecorded:standard.reduce((sum,row)=>sum+v1996PaymentAmounts(row).gross,0),grossPaid:paid.reduce((sum,row)=>sum+v1996PaymentAmounts(row).gross,0),netPaid:paid.reduce((sum,row)=>sum+v1996PaymentAmounts(row).net,0),grossReady:ready.reduce((sum,row)=>sum+v1996PaymentAmounts(row).gross,0),remaining:Math.max(0,setPrice-standard.reduce((sum,row)=>sum+v1996PaymentAmounts(row).gross,0))}
}
paymentPct=function(item){let targets=item?.type==='House Plot'?(item.lifts||[]):[item],setPrice=targets.reduce((sum,target)=>sum+v1996TargetPaymentTotals(target).setPrice,0),paid=targets.reduce((sum,target)=>sum+v1996TargetPaymentTotals(target).grossPaid,0);return setPrice?Math.min(100,Math.round(paid/setPrice*100)):0};
v190AllocationPaid=function(target,memberId){return (target?.payments||[]).filter(row=>!row.amended&&row.paidTo===memberId&&(row.paymentState||'Paid')==='Paid').reduce((sum,row)=>sum+v1996PaymentAmounts(row).gross,0)};

// New payment entries are Ready for a Pay Run, never silently marked paid.
v1993CommitPayment=function(){
  let pending=v1993PendingPayment;if(!pending||(!hasPerm('record_payments')&&!hasPerm('edit_pay_records')))return toast('Payment review expired. Start again.');let {item,target}=v190PaymentTarget(pending.context),member=v183Member(pending.memberId),figures=pending.figures;if(!item||!target||!member||!v190Allocations(target).some(row=>row.memberId===pending.memberId))return toast('The payment record changed. Start again.');let remaining=v1996TargetPaymentTotals(target).remaining;if(figures.gross<=0||figures.gross>remaining+0.005)return toast('The available set price changed. Review the payment again.');let amounts=v1996PaymentAmounts({grossAmount:figures.gross,retentionHeld:figures.retention,cisRegistered:!!member.cisRegistered,cisDeductionRate:figures.cisRate,cisGrossExVat:Math.max(0,figures.gross-figures.retention)}),record={id:'p'+Date.now(),pct:Number(target.price||0)?Math.round(amounts.gross/Number(target.price)*10000)/100:0,date:pending.date,grossAmount:amounts.gross,retentionPercent:figures.retentionRate,retentionHeld:amounts.retentionHeld,retentionRelease:0,cisRegistered:!!member.cisRegistered,cisDeductionRate:amounts.cisRate,cisGrossExVat:amounts.cisGrossExVat,cisMaterials:amounts.cisMaterials,cisLiable:amounts.cisLiable,cisDeduction:amounts.cisDeduction,cisNet:amounts.net,amount:amounts.net,paidTo:pending.memberId,payeeName:'',paymentReference:pending.reference,notes:pending.notes,paymentState:'Ready',sourceKind:'set-price',payRunId:'',paidAt:'',paidBy:'',enteredBy:currentUser()?.name||'',enteredAt:new Date().toISOString()};target.payments=target.payments||[];target.payments.push(record);target.paidTo=pending.memberId;target.paymentState='Ready';touch(item);save();audit('payments','individual payment',record.id,`${item.ref}: ${attendanceMoney(amounts.net)} net prepared for Pay Run · ${pending.reference}`,item.site);emitNotification('payment',item.site,`${item.ref}: payment prepared for Pay Run`,item.id);v1993PendingPayment=null;v1996CommitDraft();modal=null;savedState('Payment ready for Pay Run');render()
};
const v1996BaseReviewPayment=v1993ReviewPayment;
v1993ReviewPayment=function(context){let result=v1996BaseReviewPayment(context);if(modal)modal=modal.replace('This will record money as paid.','This will prepare money for the next Pay Run.').replace('Confirm and save paid','Confirm and mark ready');render();return result};
const v1996BasePaymentState=v1993PaymentState;
v1993PaymentState=function(row){if(row?.paymentState==='Ready')return'Ready for Pay Run';if(row?.paymentState==='Paid'&&row?.payRunId)return`Paid · ${row.paymentReference||'Pay Run'}`;return v1996BasePaymentState(row)};

// Variations remain manual, but approval and payment are deliberate reviews.
addVariation=function(){let item=(db.items||[]).find(row=>row.id===selected);if(!item||!hasPerm('manage_variations'))return toast('You do not have permission to create variations');modal=`<div class="modal"><div class="modalbox"><span class="att-kicker">MANUAL VARIATION</span><h2>Add variation</h2><p class="muted">${v1996Esc(item.ref)} · keep the wording exactly as instructed.</p><div class="field"><label>Title / description</label><textarea id="var-name" class="textarea" placeholder="Describe the instructed extra work"></textarea></div><div class="two"><div class="field"><label>Value</label><input id="var-value" class="input" type="number" min="0.01" step="0.01"></div><div class="field"><label>Variation date</label><input id="var-date" class="input" type="date" value="${iso()}"></div></div><div class="two"><div class="field"><label>Lift / stage</label><input id="var-stage" class="input" placeholder="Optional"></div><div class="field"><label>Assigned person / team</label><select id="var-team" class="select"><option value="">To be confirmed</option>${v1996MembersForSite(item.site).map(row=>`<option value="${row.member.id}">${v1996Esc(row.member.name)}</option>`).join('')}</select></div></div><details><summary>Instruction details</summary><div class="field"><label>Instruction reference</label><input id="var-instruction-ref" class="input"></div><div class="two"><div class="field"><label>Instructed by</label><input id="var-instruction-person" class="input"></div><div class="field"><label>Instruction date</label><input id="var-instruction-date" class="input" type="date"></div></div></details><div class="field-mobile-actions"><button class="btn light" onclick="v1996CancelFieldEditor()">Cancel</button><button class="btn orange" onclick="saveVariation()">Save for approval</button></div></div></div>`;render()};
saveVariation=function(){let item=(db.items||[]).find(row=>row.id===selected),name=(document.getElementById('var-name')?.value||'').trim(),value=Number(document.getElementById('var-value')?.value||0);if(!item||!hasPerm('manage_variations'))return toast('Variation permission is required');if(!name||value<=0)return toast('Enter the variation wording and value');let variation={id:'v'+Date.now(),name,value,date:document.getElementById('var-date')?.value||iso(),stage:(document.getElementById('var-stage')?.value||'').trim(),team:document.getElementById('var-team')?.value||item.gang||'',instructionReference:(document.getElementById('var-instruction-ref')?.value||'').trim(),instructionPerson:(document.getElementById('var-instruction-person')?.value||'').trim(),instructionDate:document.getElementById('var-instruction-date')?.value||'',payments:[],approvalStatus:'Pending Approval',createdById:currentUser()?.id||'',createdBy:currentUser()?.name||'',createdAt:new Date().toISOString(),approvedBy:'',approvedAt:''};item.variations=item.variations||[];item.variations.push(variation);touch(item);save();audit('variation','variation',variation.id,`${item.ref}: variation saved for approval — ${name}`,item.site);v1996CommitDraft();modal=null;savedState('Variation saved for approval');render()};
function v1996ConfirmVariationApproval(itemId,variationId){let item=(db.items||[]).find(row=>row.id===itemId),variation=item?.variations?.find(row=>row.id===variationId);if(!item||!variation||!hasPerm('approve_variations')||variation.approvalStatus!=='Pending Approval')return toast('This variation changed. Review it again.');if(variation.createdById===currentUser()?.id&&currentUser()?.role!=='Director')return toast('A different authorised user must approve this variation');variation.approvalStatus='Approved';variation.approvedBy=currentUser()?.name||'';variation.approvedAt=new Date().toISOString();touch(item);save();audit('variation','variation',variation.id,`${item.ref}: variation approved — ${variation.name}`,item.site);modal=null;savedState('Variation approved');render()}
approveVariation=function(variationId){let item=(db.items||[]).find(row=>row.id===selected),variation=item?.variations?.find(row=>row.id===variationId);if(!item||!variation||!hasPerm('approve_variations'))return toast('Variation approval permission is required');modal=`<div class="modal"><div class="modalbox v1996-approval-review"><span class="att-kicker">CHECK BEFORE APPROVING</span><h2>Approve variation?</h2><div class="v1996-review-grid"><span><small>Site</small><b>${v1996Esc(siteName(item.site))}</b></span><span><small>Plot / work</small><b>${v1996Esc(item.ref)}</b></span><span><small>Description</small><b>${v1996Esc(variation.name)}</b></span><span><small>Stage</small><b>${v1996Esc(variation.stage||'Not specified')}</b></span><span><small>Value</small><b>${attendanceMoney(variation.value||0)}</b></span><span><small>Instruction</small><b>${v1996Esc(variation.instructionReference||'Not entered')}</b></span><span><small>Created by</small><b>${v1996Esc(variation.createdBy||'Not recorded')}</b></span><span><small>Change</small><b>Pending Approval → Approved</b></span></div><div class="field-mobile-actions"><button class="btn light" onclick="modal=null;render()">Cancel</button><button class="btn green" onclick="v1996ConfirmVariationApproval('${item.id}','${variation.id}')">Approve ${attendanceMoney(variation.value||0)}</button></div></div></div>`;render()};
addVariationPayment=function(variationId){let item=(db.items||[]).find(row=>row.id===selected),variation=item?.variations?.find(row=>row.id===variationId);if(!item||!variation||variation.approvalStatus!=='Approved')return toast('Approve this variation before preparing payment');let remaining=Math.max(0,Number(variation.value||0)-(variation.payments||[]).filter(row=>!row.amended).reduce((sum,row)=>sum+v1996PaymentAmounts(row).gross,0));if(!remaining)return toast('This variation is fully recorded');modal=`<div class="modal"><div class="modalbox"><span class="att-kicker">VARIATION PAYMENT</span><h2>${v1996Esc(item.ref)} · ${v1996Esc(variation.name)}</h2><p class="muted">Remaining ${attendanceMoney(remaining)}. This creates a Ready record for the next Pay Run.</p><div class="two"><div class="field"><label>Gross amount</label><input id="vpa" class="input" type="number" min="0.01" max="${remaining}" step="0.01"></div><div class="field"><label>Retention held</label><div class="v191-suffix-input"><input id="v1996-vret" class="input" type="number" min="0" max="25" step="0.5" value="0"><span>%</span></div></div></div><div class="two"><div class="field"><label>Date</label><input id="vpd" class="input" type="date" value="${iso()}"></div><div class="field"><label>Invoice reference</label><input id="v1996-vref" class="input" placeholder="Required"></div></div><div class="field"><label>Individual</label><select id="vpto" class="select">${v1996MembersForSite(item.site).map(row=>`<option value="${row.member.id}" ${variation.team===row.member.id?'selected':''}>${v1996Esc(row.member.name)}</option>`).join('')}</select></div><div class="field"><label>Notes</label><textarea id="vpn" class="textarea"></textarea></div><div class="field-mobile-actions"><button class="btn light" onclick="modal=null;render()">Cancel</button><button class="btn green" onclick="saveVariationPayment('${variation.id}')">Review payment</button></div></div></div>`;render()};
let v1996PendingVariationPayment=null;
saveVariationPayment=function(variationId){let item=(db.items||[]).find(row=>row.id===selected),variation=item?.variations?.find(row=>row.id===variationId),gross=Number(document.getElementById('vpa')?.value||0),retentionRate=Number(document.getElementById('v1996-vret')?.value||0),memberId=document.getElementById('vpto')?.value||'',reference=(document.getElementById('v1996-vref')?.value||'').trim(),date=document.getElementById('vpd')?.value||iso(),notes=(document.getElementById('vpn')?.value||'').trim();if(!item||!variation||gross<=0)return toast('Enter the gross amount');if(!memberId)return toast('Choose the individual');if(!reference)return toast('Enter the invoice reference');let member=v183Member(memberId),amounts=v1996PaymentAmounts({grossAmount:gross,retentionHeld:gross*retentionRate/100,cisRegistered:!!member?.cisRegistered,cisDeductionRate:Number(member?.cisRate||20),cisGrossExVat:gross-gross*retentionRate/100});v1996PendingVariationPayment={itemId:item.id,variationId,memberId,reference,date,notes,retentionRate,amounts};modal=`<div class="modal"><div class="modalbox v1996-approval-review"><span class="att-kicker">CHECK BEFORE SAVING</span><h2>Review variation payment</h2><div class="v1996-review-grid"><span><small>Site</small><b>${v1996Esc(siteName(item.site))}</b></span><span><small>Work</small><b>${v1996Esc(item.ref)} · ${v1996Esc(variation.name)}</b></span><span><small>Individual</small><b>${v1996Esc(v183PersonName(memberId))}</b></span><span><small>Reference</small><b>${v1996Esc(reference)}</b></span><span><small>Gross</small><b>${attendanceMoney(amounts.gross)}</b></span><span><small>Retention</small><b>${attendanceMoney(amounts.retentionHeld)}</b></span><span><small>CIS retained</small><b>${attendanceMoney(amounts.cisDeduction)}</b></span><span><small>Net</small><b>${attendanceMoney(amounts.net)}</b></span></div><div class="field-mobile-actions"><button class="btn light" onclick="addVariationPayment('${variationId}')">Back</button><button class="btn green" onclick="v1996CommitVariationPayment()">Confirm ready</button></div></div></div>`;render()};
function v1996CommitVariationPayment(){let pending=v1996PendingVariationPayment,item=(db.items||[]).find(row=>row.id===pending?.itemId),variation=item?.variations?.find(row=>row.id===pending?.variationId);if(!pending||!item||!variation||variation.approvalStatus!=='Approved')return toast('This variation changed. Start again.');let amounts=pending.amounts,record={id:'vp'+Date.now(),date:pending.date,grossAmount:amounts.gross,retentionPercent:pending.retentionRate,retentionHeld:amounts.retentionHeld,retentionRelease:0,cisRegistered:amounts.cisRate>0,cisDeductionRate:amounts.cisRate,cisGrossExVat:amounts.cisGrossExVat,cisMaterials:amounts.cisMaterials,cisLiable:amounts.cisLiable,cisDeduction:amounts.cisDeduction,cisNet:amounts.net,amount:amounts.net,paidTo:pending.memberId,paymentReference:pending.reference,notes:pending.notes,variation:true,variationId:variation.id,variationName:variation.name,paymentState:'Ready',sourceKind:'variation',payRunId:'',paidAt:'',paidBy:'',enteredBy:currentUser()?.name||'',enteredAt:new Date().toISOString()};variation.payments=variation.payments||[];variation.payments.push(record);touch(item);save();audit('payments','variation payment',record.id,`${item.ref}: variation payment ready · ${pending.reference}`,item.site);v1996PendingVariationPayment=null;v1996CommitDraft();modal=null;savedState('Variation payment ready for Pay Run');render()}

function v1996PaymentEntries(){return typeof v195AllPayments==='function'?v195AllPayments():[]}
v190PriceLines=function(siteIds,from,to){
  return v1996PaymentEntries().filter(row=>siteIds.includes(row.item.site)&&String(row.payment.date||'')>=from&&String(row.payment.date||'')<=to&&row.payment.paymentState==='Ready'&&!row.payment.payRunId).map(row=>{let amounts=v1996PaymentAmounts(row.payment),member=v183Member(row.payment.paidTo);return {id:`ready:${row.payment.id}`,paymentIds:[row.payment.id],memberId:row.payment.paidTo||'',name:v183PersonName(row.payment.paidTo,row.payment.payeeName||'Individual'),role:member?.role||'',kind:row.payment.variation?'Variation':'Price Work',siteIds:[row.item.site],ref:row.item.ref,stage:row.stage||row.payment.variationName||'Payment',recorded:0,payable:0,overtime:0,earned:amounts.gross,paidBefore:0,outstanding:amounts.gross,grossDue:amounts.gross,cisRate:amounts.cisRate,cisDeduction:amounts.cisDeduction,netDue:amounts.net,retentionDue:0,sourceHash:v175Hash(JSON.stringify({id:row.payment.id,gross:amounts.gross,retention:amounts.retentionHeld,cis:amounts.cisDeduction,net:amounts.net,date:row.payment.date,reference:row.payment.paymentReference}))}}
  )
};
function v1996RunPaymentIds(run){return [...new Set((run?.snapshot?.lines||[]).flatMap(line=>line.paymentIds||[]))]}
function v1996PaymentById(id){let row=v1996PaymentEntries().find(entry=>entry.payment.id===id);return row?.payment||null}

const v1996BaseConfirmRunPaid=v1993ConfirmPayRunPaid;
v1993ConfirmPayRunPaid=function(runId){
  let run=(db.payRuns||[]).find(row=>row.id===runId),reference=(document.getElementById('v1993-run-payment-reference')?.value||'').trim();if(!reference)return toast('Enter the bank payment reference');if(!run||!['Locked','Exported'].includes(run.status)||!hasPerm('mark_payments_paid'))return toast('This Pay Run is no longer ready');let snapshot=v190PaySnapshot(run.siteIds,run.from,run.to);if(run.snapshotHash!==snapshot.hash)return toast('The pay information changed. Review and lock the run again.');let ids=v1996RunPaymentIds(run),records=ids.map(v1996PaymentById);if(records.some((payment,index)=>!payment||payment.paymentState!=='Ready'||payment.payRunId||!snapshot.lines.some(line=>(line.paymentIds||[]).includes(ids[index]))))return toast('A payment in this run changed. Review the run again.');let now=new Date().toISOString();for(const payment of records)Object.assign(payment,{paymentState:'Paid',payRunId:run.id,paidAt:now,paidBy:currentUser()?.name||'',bankPaymentReference:reference});run.status='Paid';run.paidAt=now;run.paidBy=currentUser()?.name||'';run.paymentReference=reference;run.updatedAt=now;save();audit('payments','pay run paid',run.id,`Pay Run marked paid · ${reference} · ${ids.length} linked payment record${ids.length===1?'':'s'}`,run.siteId);v1996CommitDraft();modal=null;savedState('Pay Run paid and records linked');render()
};

let v1996PendingRunAction=null;
const v1996BaseApproveRun=v190ApproveRun;
const v1996BaseLockRun=v190LockRun;
function v1996OpenRunReview(action){
  let sites=v190SelectedSites(),snapshot=v190PaySnapshot(sites,v174PayFrom,v174PayTo),run=v190CurrentRun(sites,v174PayFrom,v174PayTo),needed=action==='approve'?'Draft':'Approved';if(!run||run.status!==needed)return toast(action==='approve'?'Create a current Draft first':'Approve the current Pay Run first');if(snapshot.hash!==run.snapshotHash||snapshot.exceptions.length)return toast('The pay information changed or has exceptions. Review the run again.');v1996PendingRunAction={action,runId:run.id,siteIds:[...sites],from:v174PayFrom,to:v174PayTo,snapshotHash:snapshot.hash};
  modal=`<div class="modal"><div class="modalbox v1996-approval-review"><span class="att-kicker">PAY RUN REVIEW</span><h2>${action==='approve'?'Approve':'Lock'} Pay Run?</h2><div class="v1996-review-grid"><span><small>Period</small><b>${v1996Esc(v174RangeLabel(run.from,run.to))}</b></span><span><small>Sites</small><b>${v1996Esc(run.siteIds.map(siteName).join(' · '))}</b></span><span><small>Individuals</small><b>${snapshot.lines.length}</b></span><span><small>Gross</small><b>${attendanceMoney(snapshot.totals.grossDue||0)}</b></span><span><small>CIS retained</small><b>${attendanceMoney(snapshot.totals.cisDeduction||0)}</b></span><span><small>Retention release</small><b>${attendanceMoney(snapshot.totals.retentionDue||0)}</b></span><span><small>Net payment</small><b>${attendanceMoney(snapshot.totals.netDue||0)}</b></span><span><small>Change</small><b>${needed} → ${action==='approve'?'Approved':'Locked'}</b></span></div><div class="field-mobile-actions"><button class="btn light" onclick="modal=null;render()">Cancel</button><button class="btn ${action==='approve'?'orange':'green'}" onclick="v1996ConfirmRunAction()">Confirm ${action}</button></div></div></div>`;render()
}
v190ApproveRun=function(){if(!hasPerm('approve_pay_runs'))return toast('Pay Run approval permission is required');return v1996OpenRunReview('approve')};
v190LockRun=function(){if(!hasPerm('approve_pay_runs'))return toast('Pay Run approval permission is required');return v1996OpenRunReview('lock')};
function v1996ConfirmRunAction(){let pending=v1996PendingRunAction,run=(db.payRuns||[]).find(row=>row.id===pending?.runId);if(!pending||!run)return toast('This review expired');let snapshot=v190PaySnapshot(pending.siteIds,pending.from,pending.to),required=pending.action==='approve'?'Draft':'Approved';if(run.status!==required||snapshot.hash!==pending.snapshotHash||run.snapshotHash!==pending.snapshotHash||snapshot.exceptions.length)return toast('The pay information changed. Review the run again.');v190PaySites=[...pending.siteIds];v174PayFrom=pending.from;v174PayTo=pending.to;v1996PendingRunAction=null;modal=null;let result=pending.action==='approve'?v1996BaseApproveRun():v1996BaseLockRun();v1996CommitDraft();return result}

// ----- Mobile commercial summary and Site scope ---------------------
function v1996CommercialSiteIds(){let permitted=v1996PermittedSiteIds();return site==='all'?permitted:permitted.includes(site)?[site]:[]}
function v1996SetCommercialSite(value){setSiteContext(value);if(typeof v1992PaymentPage!=='undefined')v1992PaymentPage=1;if(typeof v190PaySites!=='undefined')v190PaySites=v1996CommercialSiteIds();render()}
function v1996CommercialScopeBar(){let ids=v1996PermittedSiteIds();if(ids.length===1)return `<div class="v1996-commercial-scope"><span>Site</span><b>${v1996Esc(siteName(ids[0]))}</b></div>`;return `<label class="v1996-commercial-scope"><span>Figures for</span><select class="select" onchange="v1996SetCommercialSite(this.value)"><option value="all" ${site==='all'?'selected':''}>All permitted Sites</option>${ids.map(id=>`<option value="${id}" ${site===id?'selected':''}>${v1996Esc(siteName(id))}</option>`).join('')}</select></label>`}
const v1996BaseFilteredPaymentRows=filteredPaymentRows;
filteredPaymentRows=function(){let allowed=new Set(v1996CommercialSiteIds());return v1996BaseFilteredPaymentRows().filter(row=>allowed.has(row.item.site))};
function v1996ManagementMoneyData(){
  if(typeof v1993ApplySavedPayPeriod==='function')v1993ApplySavedPayPeriod();if(typeof v174EnsurePayPeriod==='function')v174EnsurePayPeriod();let siteIds=v1996CommercialSiteIds(),valid=siteIds.length&&(!v174RangeValid||v174RangeValid(v174PayFrom,v174PayTo)),snapshot=valid?v190PaySnapshot(siteIds,v174PayFrom,v174PayTo):null,siteKey=[...siteIds].sort().join('|'),run=(db.payRuns||[]).filter(row=>!row.superseded&&row.siteKey===siteKey&&row.from===v174PayFrom&&row.to===v174PayTo).sort((a,b)=>String(b.updatedAt||b.createdAt).localeCompare(String(a.updatedAt||a.createdAt)))[0]||null,pendingVariations=(db.items||[]).filter(item=>siteIds.includes(item.site)).flatMap(item=>(item.variations||[]).filter(row=>!['Approved','Rejected'].includes(row.approvalStatus||'')).map(variation=>({item,variation}))),value=Number(run?.snapshot?.totals?.netDue||0);return {siteIds,from:v174PayFrom,to:v174PayTo,netDue:Number(snapshot?.totals?.netDue||0),approved:run&&['Approved','Locked','Exported'].includes(run.status)?value:0,paid:run?.status==='Paid'?value:0,payExceptions:snapshot?.exceptions||[],pendingVariations,currentRun:run}
}
function v1996OpenCommercialTab(tab){v192CommercialTab=tab;render()}
function v1996MobileMoneySummary(){let data=v1996ManagementMoneyData(),decisions=data.pendingVariations.length+data.payExceptions.length+(data.currentRun&&data.currentRun.status!=='Paid'?1:0);return `<section class="v1996-money-hero"><span class="att-kicker">CURRENT PAY PERIOD</span><h2>${v1996Esc(v174RangeLabel(data.from,data.to))}</h2><div class="v1996-money-grid"><button onclick="v1996OpenCommercialTab('history')"><small>Net payment due</small><b>${attendanceMoney(data.netDue)}</b></button><button onclick="v1996OpenCommercialTab('mobile-approvals')"><small>Approved, not paid</small><b>${attendanceMoney(data.approved)}</b></button><button class="paid" onclick="v1996OpenCommercialTab('history')"><small>Paid this period</small><b>${attendanceMoney(data.paid)}</b></button><button class="attention" onclick="v1996OpenCommercialTab('mobile-approvals')"><small>Decisions needed</small><b>${decisions}</b></button></div></section><section class="panel v1996-next-decision"><h3>Next step</h3>${decisions?`<p>Review the ${decisions} item${decisions===1?'':'s'} that need a decision before the next payment stage.</p><button class="btn orange" onclick="v1996OpenCommercialTab('mobile-approvals')">Review decisions</button>`:'<p>Nothing currently needs a money decision in this Site scope.</p>'}<button class="btn light" onclick="v1993OpenMoneyTerms()">What do these amounts mean?</button></section><div class="v1996-office-note"><b>Detailed Pay Run, CIS, exports and corrected-run tools</b><span>Use desktop or tablet for those office tasks.</span></div>`}
function v1996MobileApprovals(){let data=v1996ManagementMoneyData(),runs=(db.payRuns||[]).filter(run=>!run.superseded&&(run.siteIds||[]).some(id=>data.siteIds.includes(id))&&run.status!=='Paid').sort((a,b)=>String(b.updatedAt||b.createdAt).localeCompare(String(a.updatedAt||a.createdAt)));return `<section class="panel v1996-approval-list"><div class="sectionhead"><div><h2>Approvals</h2><p class="muted">One clear next action for each record.</p></div></div>${data.pendingVariations.map(({item,variation})=>`<article><span><b>${v1996Esc(item.ref)} · ${v1996Esc(variation.name)}</b><small>${v1996Esc(siteName(item.site))} · ${attendanceMoney(variation.value||0)} · Pending Approval</small></span>${hasPerm('approve_variations')?`<button class="btn orange" onclick="selected='${item.id}';approveVariation('${variation.id}')">Review</button>`:''}</article>`).join('')}${runs.slice(0,8).map(run=>{let action=run.status==='Draft'?'Review draft':run.status==='Approved'?'Review and lock':['Locked','Exported'].includes(run.status)?'Review payment':'Open';return `<article><span><b>${v1996Esc(v174RangeLabel(run.from,run.to))}</b><small>${v1996Esc((run.siteIds||[]).map(siteName).join(' · '))} · ${v1996Esc(run.status)} · ${attendanceMoney(run.snapshot?.totals?.netDue||0)}</small></span><button class="btn ${run.status==='Draft'?'orange':'green'}" onclick="v1993OpenRun('${run.id}');${run.status==='Draft'?'v190ApproveRun()':run.status==='Approved'?'v190LockRun()':['Locked','Exported'].includes(run.status)?'v190MarkPaid()':''}">${action}</button></article>`}).join('')||smartEmpty('No approvals waiting.','This Site scope is clear.','','','✓')}</section>`}

const v1996DesktopCommercialPage=v192CommercialPage;
v192CommercialPage=function(){
  if(!v1996IsPhone())return v1996DesktopCommercialPage();let user=currentUser(),tabs=[];
  if(['Director','Contracts Manager'].includes(user?.role)){if(!['mobile-summary','history','mobile-approvals'].includes(v192CommercialTab))v192CommercialTab='mobile-summary';tabs=[['mobile-summary','Summary'],['history','Payments'],['mobile-approvals','Approvals']]}
  else if(user?.role==='Quantity Surveyor'){if(!['saved','history','cis'].includes(v192CommercialTab))v192CommercialTab='saved';tabs=[['saved','Saved checks'],['history','Payments'],['cis','CIS']]}
  else if(user?.role==='Accounts'){if(!['reconcile','history','pay-run'].includes(v192CommercialTab))v192CommercialTab='reconcile';tabs=[['reconcile','Reconcile'],['history','Payments'],['pay-run','Pay Run']]}
  else return v1996DesktopCommercialPage();
  let body=v192CommercialTab==='mobile-summary'?v1996MobileMoneySummary():v192CommercialTab==='mobile-approvals'?v1996MobileApprovals():v192CommercialTab==='history'?paymentsHistoryPage():v192CommercialTab==='saved'?v195CommercialSavedViews():v192CommercialTab==='reconcile'?v195AccountsReconciliation():v192CommercialTab==='cis'?v192CommercialCis():v190PayRunPage();
  return `<div class="topbar"><div><span class="att-kicker">COMMERCIAL CONTROL</span><h1>Commercial</h1><div class="sub">${user.role==='Accounts'?'Reconcile and trace payment records.':user.role==='Quantity Surveyor'?'Saved checks, evidence and payment history.':'Current position and decisions that need attention.'}</div></div></div>${v1996CommercialScopeBar()}<nav class="v192-commercial-tabs v1996-commercial-tabs" aria-label="Commercial sections">${tabs.map(([id,label])=>`<button class="${v192CommercialTab===id?'active':''}" onclick="v1996OpenCommercialTab('${id}')">${label}</button>`).join('')}</nav><div class="v192-commercial-body">${body}</div>`
};

// ----- Daily role workspaces ----------------------------------------
if(typeof V173_MORE_TOOLS==='object')V173_MORE_TOOLS.dayworks=['＋','Daywork jobs','Instructed extra work assigned to you'];
if(typeof V1992_NAV_META==='object'){
  V1992_NAV_META.dashboard.label=user=>['Site Manager','Supervisor'].includes(user?.role)?'Today':'Overview';
  V1992_NAV_META.attendance.label=user=>user?.role==='Operator'?'My Time':user?.role==='Accounts'?'Time':'Team Time';
  V1992_NAV_META.commercial.label='Commercial'
}
v1992DashboardItems=function(siteId=site){return (db.items||[]).filter(item=>canViewItem(item)&&(siteId==='all'||item.site===siteId))};
v1992ExpectedPeople=function(siteId='all'){
  let sites=siteId==='all'?v1996PermittedSiteIds():[siteId],seen=new Set(),count=0;for(const id of sites)for(const row of attendanceExpectedMembers(id)||[]){let member=row.member||row;if(member?.id&&!seen.has(member.id)&&v1996CanViewMember(member)){seen.add(member.id);count++}}return count
};
v1992SiteSelector=function(){let ids=v1996PermittedSiteIds();if(ids.length===1)return `<span class="v1996-site-pill">⌖ ${v1996Esc(siteName(ids[0]))}</span>`;return `<select class="select" data-site-context="true" aria-label="Choose Site" onchange="setSiteContext(this.value)"><option value="all">All permitted Sites</option>${ids.map(id=>`<option value="${id}" ${site===id?'selected':''}>${v1996Esc(siteName(id))}</option>`).join('')}</select>`};
v1992QuickActions=function(){let actions=[];if(hasPerm('update_progress')||hasPerm('manage_workitems'))actions.push(`<button class="v1996-action--progress" onclick="v1992ChoosePlotAction('progress')"><span aria-hidden="true">◐</span><b>Update a plot</b></button>`,`<button class="v1996-action--photo" onclick="v1992ChoosePlotAction('photo')"><span aria-hidden="true">□</span><b>Take photo</b></button>`);if(hasPerm('raise_issues'))actions.push(`<button class="v1996-action--issue" onclick="v1992ChoosePlotAction('issue')"><span aria-hidden="true">!</span><b>Raise issue</b></button>`);if(hasPerm('manage_site_diary'))actions.push(`<button class="v1996-action--diary" onclick="addDiaryEntry()"><span aria-hidden="true">✎</span><b>Complete Site Diary</b></button>`);return actions.length?`<section class="v1992-quick-actions" aria-label="Quick actions">${actions.slice(0,4).join('')}</section>`:''};
function v1996FilterPlotPicker(value){let q=String(value||'').trim().toLowerCase();document.querySelectorAll('.v1996-plot-choice').forEach(button=>button.hidden=!!q&&!button.dataset.search.includes(q));let count=[...document.querySelectorAll('.v1996-plot-choice:not([hidden])')].length,status=document.getElementById('v1996-plot-count');if(status)status.textContent=`${count} matching plot${count===1?'':'s'}`}
v1992ChoosePlotAction=function(kind){let rows=v1992DashboardItems().filter(item=>kind==='issue'?hasPerm('raise_issues'):canUpdateItem(item));if(!rows.length)return toast('No accessible plots are available');let label=kind==='progress'?'Update progress':kind==='photo'?'Add photo':'Raise issue';modal=`<div class="modal"><div class="modalbox v1992-plot-picker"><h2>${label}</h2><p class="muted">Search by exact plot, work type or Site.</p><div class="searchbox v1996-plot-search"><span aria-hidden="true">⌕</span><input class="input" placeholder="e.g. Plot 27" oninput="v1996FilterPlotPicker(this.value)" autofocus></div><p id="v1996-plot-count" class="muted" role="status">${rows.length} available</p><div>${rows.sort((a,b)=>String(a.ref).localeCompare(String(b.ref),undefined,{numeric:true})).map(item=>`<button class="v1996-plot-choice" data-search="${v1996Esc([item.ref,item.type,siteName(item.site),v1992CurrentStage(item).label].join(' ').toLowerCase())}" onclick="v1992RunPlotAction('${kind}','${item.id}')"><span><b>${v1996Esc(item.ref)}</b><small>${v1996Esc(siteName(item.site))} · ${v1996Esc(v1992CurrentStage(item).label)}</small></span><strong>${kind==='progress'?(overall(item)==='Not started'?'Start':'Update'):'Choose'} ›</strong></button>`).join('')}</div><button class="btn light" onclick="modal=null;render()">Cancel</button></div></div>`;render()};

const v1996BaseAttendanceTodayView=attendanceTodayView;
attendanceTodayView=function(siteId){
  if(!v1996IsPhone()||!['Site Manager','Supervisor'].includes(currentUser()?.role))return v1996BaseAttendanceTodayView(siteId);let today=iso(),expected=attendanceVisibleMembers(siteId).filter(row=>v1996CanViewMember(row.member)),active=expected.filter(row=>attendanceActiveShift(row.member.id)?.siteId===siteId),missing=expected.filter(row=>!attendanceActiveShift(row.member.id)&&!attendanceAbsence(row.member.id,today,siteId)),attempts=(db.attendanceAttempts||[]).filter(row=>row.siteId===siteId&&attendanceDateOnly(row.time)===today),pending=(db.attendanceShifts||[]).filter(row=>row.siteId===siteId&&row.overtimeApproval?.status==='Pending').length,review=attempts.length+pending,clock=hasPerm('clock_attendance')&&currentUser()?.linkedMemberId?attendanceTodayCard():'';
  return `${clock}<section class="panel v1996-team-today"><span class="att-kicker">TEAM TIME · ${v1996Esc(siteName(siteId))}</span><h2>Today at a glance</h2><div class="v1996-team-queues"><button onclick="document.getElementById('v1996-clocked-list')?.toggleAttribute('open')"><small>Clocked in</small><b>${active.length}</b></button><button class="${missing.length?'attention':''}" onclick="document.getElementById('v1996-missing-list')?.toggleAttribute('open')"><small>Missing / late</small><b>${missing.length}</b></button><button class="${review?'attention':''}" onclick="contractorAttendanceTab='timesheets';attendanceTab='timesheets';render()"><small>Needs review</small><b>${review}</b></button></div><details id="v1996-clocked-list"><summary>Clocked in people</summary>${active.map(row=>`<p><b>${v1996Esc(row.member.name)}</b><span>${v1996Esc(row.gang?.name||'Site team')}</span></p>`).join('')||'<p>None currently clocked in.</p>'}</details><details id="v1996-missing-list"><summary>Expected but not clocked in</summary>${missing.map(row=>`<p><b>${v1996Esc(row.member.name)}</b><span>${v1996Esc(row.gang?.name||'Site team')}</span></p>`).join('')||'<p>Everyone expected is accounted for.</p>'}<p class="muted">Use desktop or tablet for geofence setup and working-pattern changes.</p></details><div class="v1996-team-actions"><button class="btn orange" onclick="contractorAttendanceTab='timesheets';attendanceTab='timesheets';render()">Review next timesheet</button><button class="btn light" onclick="contractorAttendanceTab='timesheets';attendanceTab='timesheets';render()">Timesheets and corrections</button></div></section>`
};
const v1996BaseMyTimeView=attendanceMyTimeOnlyView;
attendanceMyTimeOnlyView=function(siteId,linked){let html=v1996BaseMyTimeView(siteId,linked);if(!linked)return html;let from=attendanceWeekStartISO(new Date()),to=v174DateAdd(from,6),total=v174AttendanceTotalsForRange(linked.member.id,from,to,siteId),excluded=Math.round(Number(total.punctualityDeductedHours||0)*60),connection=navigator.onLine?'Connected — location will be checked when you clock.':'No connection — verified clocking is unavailable.',status=`<div class="v1996-clock-readiness ${navigator.onLine?'ready':'offline'}"><span aria-hidden="true">${navigator.onLine?'✓':'!'}</span><div><b>${connection}</b><small>${navigator.onLine?'GPS is only requested after you press Clock In or Clock Out.':'Save a clock request for manager review if you need to record this time.'}</small></div>${!navigator.onLine&&typeof v177OpenOfflineClockRequest==='function'?`<button class="btn orange" onclick="v177OpenOfflineClockRequest('${attendanceActiveShift(linked.member.id)?'out':'in'}')">Save clock request</button>`:''}</div>`,deduction=excluded?`<div class="v1996-worker-deduction"><b>${excluded} minute${excluded===1?'':'s'} excluded from payable time this week.</b><span>This only applies when the saved late-arrival or early-leaving grace is exceeded.</span><button class="btn light smallbtn" onclick="toast('Tell your Site Manager which clocking time needs correcting.')">Tell manager this time is wrong</button></div>`:'';let overtime=typeof v188OvertimeRequestCard==='function'?v188OvertimeRequestCard():'';let marker='<div class="att-shift-list">';return html.includes(marker)?html.replace(marker,status+deduction+overtime+marker):status+deduction+overtime+html};
const v1996BaseAttendanceDoClock=attendanceDoClock;
attendanceDoClock=async function(action){if(!navigator.onLine&&!window.SiteSightNative&&typeof v177OpenOfflineClockRequest==='function')return v177OpenOfflineClockRequest(action==='clock-out'?'out':'in');return v1996BaseAttendanceDoClock(action)};

// ----- Correct Demo accounts and genuine per-role test routes -------
const V1996_DEMO_ACCOUNTS=[['Director','director'],['Contracts Manager','contracts.manager'],['Site Manager','site.kingsbrook'],['Supervisor','supervisor.kingsbrook'],['Quantity Surveyor','qs'],['Accounts','accounts'],['Operator','operator.lewis'],['Read Only','client.viewer']];
function v1996FillDemoLogin(username){let user=(db.users||[]).find(row=>row.username===username);if(!user)return;let userField=document.getElementById('login-user'),password=document.getElementById('login-pass');if(userField)userField.value=user.username;if(password)password.value='Demo2026!';password?.focus()}
const v1996BaseLoginScreen=loginScreen;
loginScreen=function(){v1996BaseLoginScreen();if(!db.demoProfile)return;let details=document.querySelector('.v1992-demo-logins');if(details)details.innerHTML=`<summary>Choose a user type to test</summary><p>Each user sees different tools, Sites and permissions. All fictional Demo accounts use password <b>Demo2026!</b>.</p><div class="v1996-demo-role-grid">${V1996_DEMO_ACCOUNTS.map(([roleName,username])=>{let user=(db.users||[]).find(row=>row.username===username)|| (db.users||[]).find(row=>row.role===roleName);return user?`<button type="button" onclick="v1996FillDemoLogin('${v1996Esc(user.username)}')"><b>${v1996Esc(roleName)}</b><small>${v1996Esc(user.name)} · ${v1996Esc(user.username)}</small><span>Use this test login</span></button>`:''}).join('')}</div>`;let intro=document.querySelector('.v1992-login section>p');if(intro)intro.textContent='Two active Sites, 80 homes and one archived Site — choose a test user below.'};
const v1996AccessibleLoginScreen=loginScreen;
loginScreen=function(){v1996AccessibleLoginScreen();let username=document.getElementById('login-user'),password=document.getElementById('login-pass'),details=document.querySelector('.v1992-demo-logins');if(username){let label=username.closest('.field')?.querySelector('label');if(label)label.htmlFor=username.id}if(password){let label=password.closest('.field')?.querySelector('label');if(label)label.htmlFor=password.id}if(details)details.open=true;let toggle=password?.parentElement?.querySelector('.auth-password-toggle');if(toggle){toggle.setAttribute('aria-controls','login-pass');toggle.setAttribute('aria-pressed',String(password.type!=='password'));toggle.setAttribute('aria-label',password.type==='password'?'Show password':'Hide password')}};
const v1996BaseToggleLoginPassword=toggleLoginPassword;
toggleLoginPassword=function(id,button){v1996BaseToggleLoginPassword(id,button);let input=document.getElementById(id),showing=input?.type==='text';if(button){button.setAttribute('aria-controls',id);button.setAttribute('aria-pressed',String(!!showing));button.setAttribute('aria-label',showing?'Hide password':'Show password')}};

if(typeof V1991_TEST_ROUTES==='object'){
  V1991_TEST_ROUTES.Director=[['dashboard','Compare both active Sites','Open each Site and check its completion and exceptions.'],['work-items','Find one exact plot','Use Search rather than scrolling.'],['attendance','Review Team Time','Open one attendance exception.'],['commercial','Review Commercial','Check the current position and one pending decision.'],['more','Open Menu and Notifications','Confirm Help, Notifications and Sign out are obvious.']];
  V1991_TEST_ROUTES['Contracts Manager']=[['dashboard','Compare Sites','Open one Site needing attention.'],['work-items','Review a plot','Check current lift and evidence.'],['attendance','Review Team Time','Open one exception or timesheet.'],['commercial','Review Commercial decisions','Follow the single next action.'],['more','Use Notifications and Help','Open a notification and find Sign out.']];
  V1991_TEST_ROUTES['Site Manager']=[['dashboard','Start on Today','Check completion and the attention list.'],['work-items','Update a plot','Save one lift-stage change.'],['issues','Record a problem','Use the short form and optional details.'],['attendance','Review Team Time','Check My Clock and the three queues.'],['diary','Complete Site Diary','Save one factual daily record.']];
  V1991_TEST_ROUTES.Supervisor=[['dashboard','Start on Today','Find the next operational action.'],['work-items','Update assigned work','Add progress or photo evidence.'],['issues','Review an issue','Check responsibility and target date.'],['attendance','Review Team Time','Check only permitted Site people appear.'],['diary','Update Site Diary','Save job-specific wording.']];
  V1991_TEST_ROUTES['Quantity Surveyor']=[['commercial','Open Saved checks','Trace a figure to its evidence.'],['work-items','Review progress evidence','Compare lifts and plot completion.'],['site-drawings','Open a drawing','Review one measured record.'],['documents','Find a revision','Confirm current and superseded status.']];
  V1991_TEST_ROUTES.Accounts=[['commercial','Open Reconcile','Review a current difference.'],['commercial','Find a payment reference','Use Search and Payment History.'],['documents','Find supporting evidence','Open one permitted document.']];
  delete V1991_TEST_ROUTES.Subcontractor;
  V1991_TEST_ROUTES['Read Only']=[['dashboard','Read the Overview','Confirm metrics do not offer forbidden actions.'],['work-items','Inspect plot completion','Compare stages and percentage.'],['site-drawings','Open a drawing','Check read-only access.'],['calendar','Review Calendar','Find an upcoming record.']];
  V1991_TEST_ROUTES.Operator=[['my-work','Open My Work','Confirm assigned plots appear as visual cards.'],['issues','Review relevant issues','Check no unrelated Site issue appears.'],['attendance','Use My Time','Check clock status and payable time.'],['more','Find Help and Sign out','Open Menu from the header.']]
}
// To test another user type correctly, sign out and choose that Demo login.
v1991TestRoutePage=function(){return newUserGuidePage()};

// ----- Demo migration: additive only; manual wording is untouched ----
function v1996EachPayment(callback){for(const item of db.items||[]){for(const target of item.type==='House Plot'?(item.lifts||[]):[item])for(const payment of target.payments||[])callback(payment,item,target);for(const variation of item.variations||[])for(const payment of variation.payments||[])callback(payment,item,variation)}}
function v1996Migrate(persist=true){let changed=false;
  if(db.demoProfile){for(const [roleName,username] of V1996_DEMO_ACCOUNTS){let user=(db.users||[]).find(row=>row.username===username);if(user&&['Director','Contracts Manager','Quantity Surveyor','Accounts','Read Only'].includes(roleName)&&user.allSitesAccess!==true){user.allSitesAccess=true;changed=true}}
    if(!(db.items||[]).some(item=>item.type==='Daywork')){let templates=[{id:'demo-daywork-willow',site:'demo-site-25',ref:'Daywork DW-014',houseType:'Boundary wall remedial work',assignedId:'m-kh1',status:'Started',notes:'Instruction: rebuild damaged return beside Plot 014. Record labour hours, materials used and photographs before closing.'},{id:'demo-daywork-riverside',site:'demo-site-96',ref:'Daywork DW-027',houseType:'Service opening alterations',assignedId:'m-kh4',status:'Not started',notes:'Instruction: alter two service openings at Plot 027 to revised drawing. Confirm hours and brick quantity on completion.'}];for(const row of templates)db.items.push({...row,gang:'individuals',price:0,pct:0,paidTo:row.assignedId,date:'',paymentNotes:'',completedDate:'',payments:[],signed:false,drawings:[],workPhotos:[],issues:[],variations:[],lastUpdated:iso(),lastUpdatedAt:new Date().toISOString(),lastUpdatedBy:'SiteSight Demo',createdAt:new Date().toISOString(),createdBy:'SiteSight Demo'});changed=true}
  }
  v1996EachPayment((payment,item)=>{let amounts=v1996PaymentAmounts(payment),explicitGross=payment.grossAmount!==undefined;if(payment.paymentState===undefined){payment.paymentState='Paid';changed=true}if(payment.payRunId===undefined){payment.payRunId='';changed=true}if(payment.sourceKind===undefined){payment.sourceKind=Number(payment.retentionRelease||0)>0?'retention':payment.variation?'variation':'set-price';changed=true}if(explicitGross&&Math.abs(Number(payment.amount||0)-amounts.net)>.02&&!payment.needsFinancialReview){payment.needsFinancialReview=true;changed=true}});
  for(const item of db.items||[])for(const issue of item.issues||[])if(!issue.createdById&&issue.createdBy){let user=(db.users||[]).find(row=>row.name===issue.createdBy);if(user){issue.createdById=user.id;changed=true}}
  if(changed&&persist)save();return changed
}
const v1996BaseMigrate=migrate;
migrate=function(persist=true){let changed=!!v1996BaseMigrate(false),enhanced=v1996Migrate(false);if((changed||enhanced)&&persist)save();return changed||enhanced};

// ----- Sync state that makes sense to a person on a phone -----------
function v1996PendingEnvelopeKey(userId=currentUserId){return `sitesight-v1996-pending:${userId||'unknown'}`}
function v1996StorePendingEnvelope(){if(!sharedMode||!currentUserId)return;try{localStorage.setItem(v1996PendingEnvelopeKey(),JSON.stringify({userId:currentUserId,baseRevision:sharedRevision,baseData:lastSyncedDB||null,localData:db,createdAt:new Date().toISOString()}))}catch(error){}}
function v1996ClearPendingEnvelope(){try{localStorage.removeItem(v1996PendingEnvelopeKey())}catch(error){}}
const v1996BaseSave=save;
save=function(){let result=v1996BaseSave();if(sharedMode&&pendingSave)v1996StorePendingEnvelope();return result};
const v1996BaseSyncToServer=syncToServer;
syncToServer=async function(...args){let result=await v1996BaseSyncToServer(...args);if(pendingSave)v1996StorePendingEnvelope();else v1996ClearPendingEnvelope();return result};
function v1996SyncUi(){
  if(window.SITESIGHT_HOSTED_DEMO||!sharedMode)return {state:'local',label:'This device only',detail:'Public test changes stay on this phone'};
  if(syncState==='saving')return {state:'saving',label:'Saving…',detail:'Sending saved changes'};
  if(syncState==='offline'||!navigator.onLine)return {state:'offline',label:'Offline — saved here',detail:'Changes will send when connected'};
  if(syncState==='error')return {state:'error',label:/session/i.test(syncError||'')?'Sign in to send':'Sync needs attention',detail:syncError||'Open Menu and retry'};
  if(pendingSave)return {state:'saving',label:'Waiting to share',detail:'Saved on this phone'};
  return {state:'synced',label:'Saved and shared',detail:'Up to date'}
}
function v1996RetrySync(){if(!sharedMode)return toast('This public test saves only on this device');syncToServer(true)}
const v1996BaseSavedState=savedState;
savedState=function(message='Saved'){let copy=sharedMode?(pendingSave?'Saved on this phone — waiting to sync':'Saved and shared'):'Saved on this device',result=v1996BaseSavedState(message||copy);v1996Announce(message||copy);return result};

// ----- Mobile navigation, shell and accessibility -------------------
function v1996NavLabel(item,user=currentUser()){let meta=typeof V1992_NAV_META==='object'?V1992_NAV_META[item.id]:null,label=meta?.label??item.label??item.id;return typeof label==='function'?label(user):label}
function v1996MobileNavButton(item){let id=item.id,label=v1996NavLabel(item),count=id==='issues'?scopeItems().reduce((sum,row)=>sum+(row.issues||[]).filter(issue=>issue.status==='Open'&&issueRelevantToFieldUser(row,issue)).length,0):0,icon=item.icon||V1992_NAV_META?.[id]?.icon||'□';return `<button type="button" data-nav="${v1996Esc(id)}" class="${v1995MobileNavActive(id)?'active':''}" onclick="goNav('${id}')" aria-label="Open ${v1996Esc(label)}${count?` · ${count} open`:''}"><span class="v1995-nav-tile" aria-hidden="true">${icon}</span><b>${v1996Esc(label)}</b>${count?`<em class="count">${count}</em>`:''}</button>`}
mobileNav=function(){let user=currentUser();if(!user||(user.role==='Operator'&&!v1998OperatorHasWork(user)))return'';let rows=v173RoleNavItems(user).slice(0,4),html=rows.map(v1996MobileNavButton).join('');if(v1992HasMore(user))html+=v1996MobileNavButton({id:'more',icon:'•••',label:'More'});return html};

function v1996EnsureLiveRegions(){if(!document.getElementById('v1996-live')){let polite=document.createElement('div');polite.id='v1996-live';polite.className='sr-only';polite.setAttribute('role','status');polite.setAttribute('aria-live','polite');document.body.appendChild(polite)}if(!document.getElementById('v1996-alert')){let alert=document.createElement('div');alert.id='v1996-alert';alert.className='sr-only';alert.setAttribute('role','alert');alert.setAttribute('aria-live','assertive');document.body.appendChild(alert)}}
function v1996Announce(message,error=false){v1996EnsureLiveRegions();let region=document.getElementById(error?'v1996-alert':'v1996-live');if(region){region.textContent='';setTimeout(()=>region.textContent=String(message||''),10)}}
const V1996_LANDSCAPE_HINT_KEY='sitesight-v1996-landscape-hint';
let v1996LandscapeHintShown=false,v1996LandscapeHintFocus=null,v1996LandscapeHintOverflow='';
function v1996IsHandheldDevice(){
  let coarse=window.matchMedia?.('(pointer:coarse)').matches||Number(navigator.maxTouchPoints||0)>0,mobileUa=navigator.userAgentData?.mobile===true||/Android|iPhone|iPod|IEMobile|Mobile/i.test(navigator.userAgent||''),shortSide=Math.min(window.innerWidth||0,window.innerHeight||0);
  return v1996IsPhone()||(shortSide<=720&&(coarse||mobileUa))
}
function v1996LandscapeHintKeydown(event){if(event.key==='Escape'){event.preventDefault();v1996CloseLandscapeHint()}}
function v1996CloseLandscapeHint(){
  let hint=document.getElementById('v1996-landscape-hint');if(!hint)return;hint.remove();document.removeEventListener('keydown',v1996LandscapeHintKeydown);let root=document.getElementById('root');if(root)root.inert=false;document.body.style.overflow=v1996LandscapeHintOverflow;if(v1996LandscapeHintFocus?.isConnected)v1996LandscapeHintFocus.focus({preventScroll:true});v1996LandscapeHintFocus=null
}
function v1996MaybeShowLandscapeHint(){
  if(v1996LandscapeHintShown||!v1996IsHandheldDevice()||window.innerWidth>window.innerHeight||document.getElementById('v1996-landscape-hint')||document.querySelector('.modal'))return;
  try{if(sessionStorage.getItem(V1996_LANDSCAPE_HINT_KEY))return}catch(error){}
  v1996LandscapeHintShown=true;try{sessionStorage.setItem(V1996_LANDSCAPE_HINT_KEY,'shown')}catch(error){}v1996LandscapeHintFocus=document.activeElement;v1996LandscapeHintOverflow=document.body.style.overflow;let root=document.getElementById('root');if(root)root.inert=true;document.body.style.overflow='hidden';
  let hint=document.createElement('div');hint.id='v1996-landscape-hint';hint.className='v1996-landscape-hint';hint.innerHTML=`<div class="v1996-landscape-card" role="dialog" aria-modal="true" aria-labelledby="v1996-landscape-title" aria-describedby="v1996-landscape-copy"><span class="v1996-landscape-phone" aria-hidden="true">↻</span><div><span class="att-kicker">PHONE DISPLAY</span><h2 id="v1996-landscape-title">Turn your phone sideways</h2><p id="v1996-landscape-copy">This app works best in landscape mode.</p><button type="button" class="btn orange" onclick="v1996CloseLandscapeHint()">Continue</button></div></div>`;document.body.appendChild(hint);document.addEventListener('keydown',v1996LandscapeHintKeydown);requestAnimationFrame(()=>hint.querySelector('button')?.focus())
}
function v1996HandleOrientationChange(){if(window.innerWidth>window.innerHeight&&document.getElementById('v1996-landscape-hint'))return v1996CloseLandscapeHint();v1996MaybeShowLandscapeHint()}
const v1996BaseToast=toast;
toast=function(message){let result=v1996BaseToast(message);v1996Announce(message,/error|failed|cannot|outside|permission|unavailable|incorrect/i.test(String(message)),false);return result};
function v1996EnhanceRenderedUi(){
  let root=document.getElementById('root'),user=currentUser();if(!root||!user)return;root.classList.toggle('v1996-operator',user.role==='Operator'&&!v1998OperatorHasWork(user));root.classList.toggle('v1998-field-operator',v1998OperatorHasWork(user));root.classList.toggle('v1996-phone',v1996IsPhone());
  root.querySelectorAll('label:not([for])').forEach(label=>{let control=label.querySelector('input[id],select[id],textarea[id]')||label.parentElement?.querySelector(':scope > input[id],:scope > select[id],:scope > textarea[id],:scope > div input[id],:scope > div select[id],:scope > div textarea[id]');if(control)label.htmlFor=control.id});
  let main=root.querySelector('.main');if(main){main.id='main-content';main.tabIndex=-1}
  let nav=root.querySelector('.mobilebar');if(nav){nav.setAttribute('role','navigation');nav.setAttribute('aria-label','Main navigation')}
  let header=root.querySelector('.v1995-mobile-header');if(header&&!header.querySelector('.v1996-sync-chip')){let state=v1996SyncUi(),button=document.createElement('button');button.type='button';button.className=`v1996-sync-chip ${state.state}`;button.setAttribute('aria-label',`${state.label}. ${state.detail}`);button.innerHTML=`<i aria-hidden="true"></i><span><b>${v1996Esc(state.label)}</b><small>${v1996Esc(state.detail)}</small></span>`;button.onclick=()=>state.state==='synced'||state.state==='local'?v1995OpenMobileAccount():v1996RetrySync();header.insertBefore(button,header.querySelector('.v1995-header-account'))}
  let account=root.querySelector('.v1995-account-menu');if(account&&!account.querySelector('.v1996-account-sync')){let state=v1996SyncUi();account.querySelector('.v1995-account-identity')?.insertAdjacentHTML('afterend',`<button class="v1996-account-sync ${state.state}" onclick="v1996RetrySync()"><span><b>${v1996Esc(state.label)}</b><small>${v1996Esc(state.detail)}</small></span>${['offline','error','saving'].includes(state.state)?'<strong>Retry</strong>':''}</button>`)}
  root.querySelectorAll('.v1992-quick-actions>button').forEach(button=>{let text=button.textContent||'';if(/Update/i.test(text))button.classList.add('v1996-action--progress');if(/photo/i.test(text))button.classList.add('v1996-action--photo');if(/issue/i.test(text))button.classList.add('v1996-action--issue');if(/Diary/i.test(text))button.classList.add('v1996-action--diary')});
  root.querySelectorAll('.summary .card .muted').forEach(label=>{if(label.textContent.trim()==='Paid')label.textContent='Overall completion'});
  if(user.role==='Read Only')root.querySelectorAll('.v1992-today-metrics button').forEach(button=>{let text=button.textContent||'',target=/Clocked|Expected/i.test(text)?'attendance':/Open issues/i.test(text)?'issues':'work-items';if(!v173CanOpenView(target,user)){button.disabled=true;button.removeAttribute('onclick');button.setAttribute('aria-label',`${text.trim()} — view only`)}});
  root.querySelectorAll('.v1994-back-overlay').forEach(button=>{button.setAttribute('aria-label','Back to the previous screen');let span=button.querySelector('span');if(span)span.textContent='Back'});
  let help=document.getElementById('sitesight-assist-fab');if(help){help.setAttribute('aria-label','Open help for this screen');let bold=help.querySelector('b');if(bold)bold.textContent='Help'}
  let search=root.querySelector('.global-search-modal');if(search){let intended=search.querySelector('[data-dialog-close]');search.querySelectorAll('.v1991-modal-close').forEach(button=>{if(button!==intended)button.remove()})}
  root.querySelectorAll('.v1992-site-progress,.v1992-site-card').forEach(row=>row.classList.add('v1996-visual-card'));
  root.querySelectorAll('.att-table-wrap').forEach(wrap=>{if(!wrap.previousElementSibling?.classList.contains('v1996-swipe-hint'))wrap.insertAdjacentHTML('beforebegin','<p class="v1996-swipe-hint"><span aria-hidden="true">↔</span> Swipe sideways to see every day. The person’s name stays visible.</p>')});
  let version=root.querySelector('.company > div');if(version)version.textContent=`v1.99.8 · PROFESSIONAL DEMO${db.demoProfile?' DEMO':''}`;
  let banner=root.querySelector('.demo-mode-banner');if(banner&&db.demoProfile){let title=banner.querySelector('b'),copy=banner.querySelector('span');if(title)title.textContent='PERSONAL TEST DEMO';if(copy)copy.textContent='2 active Sites · 80 homes · 1 archived Site'}
  root.querySelectorAll('[onclick*="approveVariation"]').forEach(button=>{let card=button.closest('.card,article,details');let match=button.getAttribute('onclick')?.match(/approveVariation\('([^']+)'\)/);if(card&&match)card.dataset.variationId=match[1]});
  v1996EnhanceFieldEditor();v1996EnsureLiveRegions()
}

const v1996BaseShell=shell;
shell=function(content){let result=v1996BaseShell(content);v1996EnhanceRenderedUi();return result};

// Back history now includes the filters that make a page meaningful.
v1994ReadState=function(){return {view,site,selected:selected||'',selectedGang:selectedGang||'',q,issueSearch,issueFocusFilter,paymentSearch,paymentFrom,paymentTo,contractorAttendanceTab:typeof contractorAttendanceTab==='undefined'?'':contractorAttendanceTab,attendanceTab:typeof attendanceTab==='undefined'?'':attendanceTab,attendanceSite:typeof attendanceSite==='undefined'?'':attendanceSite,commercialTab:typeof v192CommercialTab==='undefined'?'':v192CommercialTab,adminTab:typeof adminTab==='undefined'?'':adminTab,documentFolder:typeof v191DocumentFolder==='undefined'?'':v191DocumentFolder,scrollY:window.scrollY||0}};
v1994RestoreState=function(state){view=state.view;site=state.site;selected=state.selected||null;selectedGang=state.selectedGang||null;q=state.q||'';issueSearch=state.issueSearch||'';issueFocusFilter=state.issueFocusFilter||'all';paymentSearch=state.paymentSearch||'';paymentFrom=state.paymentFrom||'';paymentTo=state.paymentTo||'';if(typeof contractorAttendanceTab!=='undefined')contractorAttendanceTab=state.contractorAttendanceTab||'today';if(typeof attendanceTab!=='undefined')attendanceTab=state.attendanceTab||'today';if(typeof attendanceSite!=='undefined')attendanceSite=state.attendanceSite||'all';if(typeof v192CommercialTab!=='undefined')v192CommercialTab=state.commercialTab||v192CommercialTab;if(typeof adminTab!=='undefined')adminTab=state.adminTab||adminTab;if(typeof v191DocumentFolder!=='undefined')v191DocumentFolder=state.documentFolder||'';requestAnimationFrame(()=>window.scrollTo(0,Number(state.scrollY||0)))};

let v1996LastRouteKey='';
const v1996BaseRender=render;
render=function(){
  if(currentUser())v1996NormaliseSiteContext();let route=currentUser()?JSON.stringify([currentUserId,view,site,selected||'',selectedGang||'',typeof attendanceTab==='undefined'?'':attendanceTab,typeof contractorAttendanceTab==='undefined'?'':contractorAttendanceTab,typeof v192CommercialTab==='undefined'?'':v192CommercialTab,typeof v191DocumentFolder==='undefined'?'':v191DocumentFolder]):'',changed=!!route&&route!==v1996LastRouteKey,result=v1996BaseRender();v1996LastRouteKey=route;v1996EnhanceRenderedUi();if(changed&&v1996IsPhone()&&!modal&&!v1994Restoring){v1995ScrollTop();requestAnimationFrame(()=>{let heading=document.querySelector('.main h1');if(heading){heading.tabIndex=-1;heading.focus({preventScroll:true});v1996Announce(`${heading.textContent.trim()} — ${v1996SiteLabel()}`)}})}if(currentUser()&&v1996PendingLaunchTarget)setTimeout(v1996ApplyPendingLaunchTarget,0);setTimeout(v1996MaybeShowLandscapeHint,0);return result
};

const v1996BaseEmitNotification=emitNotification;
emitNotification=function(kind,siteId,text,itemId='',metadata={}){let result=v1996BaseEmitNotification(kind,siteId,text,itemId),row=(db.notifications||[])[0];if(row){Object.assign(row,metadata||{});let target=v1994NotificationTarget(kind,siteId,text,itemId);if(!row.targetType)Object.assign(row,target);save()}return result};

// Apply the additive migration to the already-loaded hosted Demo and
// refresh once so every final wrapper is active.
migrate(false);
if(currentUser()){v1996NormaliseSiteContext();render()}else loginScreen();
setTimeout(v1996MaybeShowLandscapeHint,0);
window.addEventListener('orientationchange',()=>setTimeout(v1996HandleOrientationChange,180));
