/* =====================================================================
   NOWILLS SQC ERP — Custom panel mounters (live & drag-and-drop views)
   window.MOUNT[id](el, section, module) → renders bespoke panel content
   ===================================================================== */
(function(){
'use strict';
const D=DATA,C=Charts.palette;const MOUNT={};window.MOUNT=MOUNT;
const esc=s=>String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;');
function f(){return (window.NWX&&NWX.scope)?NWX.scope().f:1;}
function fm(v){return window.NWX?NWX.fmtM(v):v;}
function kv(v){const a=Math.abs(v);if(a>=1e6)return(v/1e6).toFixed(1)+'M';if(a>=1e3)return(v/1e3).toFixed(1)+'k';return Math.round(v)+'';}

/* ---------------- GIS sites & live fleet ---------------- */
const SITEC={'Shai Hills':[-0.06,5.895],'Buoho':[-1.61,6.71],'Nsawam South':[-0.38,5.84],'Tema Yard':[-0.01,5.70],'Tema Port':[0.00,5.62],'Kpone LP':[0.05,5.72],'Tema Interchange':[0.00,5.68],'Kumasi North':[-1.62,6.78],'Accra–Kumasi Rd':[-0.90,6.10],'Buoho Twp Roads':[-1.63,6.70],'Nsawam–Pokuase':[-0.30,5.79],'Kpone Logistics Park':[0.05,5.72],'—':[-0.25,5.90]};
if(!window.TRUCKS){
 window.TRUCKS=D.FLEET.filter(v=>v[2]==='In transit').map(v=>{const o=SITEC[v[4]]||SITEC['Shai Hills'],d=SITEC[v[5]]||SITEC['Tema Port'];
  return {reg:v[0],model:v[1],driver:v[7],speed:v[3],origin:v[4],dest:v[5],load:v[6],lon:o[0]+(d[0]-o[0])*(0.2+Math.random()*0.6),lat:o[1]+(d[1]-o[1])*(0.2+Math.random()*0.6),alert:v[0]==='TRK-1260'};});
}
function truckMarkers(){return TRUCKS.map(t=>({kind:'truck',lon:t.lon,lat:t.lat,label:t.reg+' · '+t.driver,tag:t.reg.replace('TRK-',''),status:t.alert?'alert':'',detail:t.origin+' → '+t.dest+' · '+t.load+' · '+t.speed+' km/h'}));}
function quarryMarkers(){return D.QUARRIES.slice(1).map(q=>({kind:'quarry',lon:q.lon,lat:q.lat,label:q.n,tag:q.id,detail:'Production share '+Math.round(q.f*100)+'% · ROM stock live'}));}
function projectMarkers(){return D.PROJECTS.filter(p=>p.st!=='done').map(p=>({kind:'project',lon:p.lon,lat:p.lat,label:p.id+' — '+p.n,tag:p.id,status:p.risk==='high'?'alert':'',detail:p.client+' · '+p.comp+'% complete'}));}
MOUNT.gmap=function(el){ const spec={type:'map',title:'GIS — operations map',markers:quarryMarkers().concat(projectMarkers(),truckMarkers()),layers:['quarry','project','truck'],height:400,drillDim:'GIS entity'};
 const inst=Charts.render(el,spec); NWX.addLive(()=>{moveTrucks(); inst.spec.markers=quarryMarkers().concat(projectMarkers(),truckMarkers()); inst.draw();});};
MOUNT.fleetmap=function(el){ const deps=[{kind:'depot',lon:-0.01,lat:5.70,label:'Tema Yard Depot',tag:'DEP',detail:'Dispatch hub · 12 bays'}];
 const spec={type:'map',title:'Live fleet',markers:deps.concat(truckMarkers()),layers:['depot','truck'],height:430};
 const inst=Charts.render(el,spec.el?spec.el:spec); NWX.addLive(()=>{moveTrucks(); inst.spec.markers=deps.concat(truckMarkers()); inst.draw();});};
function moveTrucks(){TRUCKS.forEach(t=>{const d=SITEC[t.dest]||[0,5.6];const dx=d[0]-t.lon,dy=d[1]-t.lat;
  const dist=Math.hypot(dx,dy);if(dist<0.03){const tmp=t.origin;t.origin=t.dest;t.dest=tmp;}else{t.lon+=dx*0.06+(Math.random()-0.5)*0.006;t.lat+=dy*0.06+(Math.random()-0.5)*0.006;
  t.speed=Math.max(38,Math.min(84,Math.round(t.speed+(Math.random()-0.5)*8)));}});}

/* ---------------- Quick actions strip (exec) ---------------- */
MOUNT.qa=function(el){ const A=[['Create Sales Order','tag'],['Weighbridge Ticket','scale'],['Record Production','quarry'],['Create Project','hardhat'],['Submit Site Report','site'],['Purchase Requisition','cart'],['Record Goods Receipt','wh'],['Issue Stock','pile'],['Maintenance Request','wrench'],['Create Work Order','wrench'],['Record Fuel Issue','fuel'],['Customer Invoice','doc'],['Record Customer Payment','cash'],['Supplier Invoice','truck'],['Payment Voucher','bank'],['Create Journal','budget'],['Budget Request','budget'],['Record Safety Incident','shield'],['Upload Document','docs'],['Generate Report','report']];
 el.innerHTML='<div class="tiles">'+A.map(a=>'<div class="tile" data-a="'+a[0]+'">'+NWX.icon(a[1])+'<span>'+a[0]+'</span></div>').join('')+'</div>';
 el.querySelectorAll('.tile').forEach(t=>t.onclick=()=>NWX.quickForm(t.dataset.a)); };

/* ---------------- Live crusher floor ---------------- */
MOUNT.crushlive=function(el){ function render(){ el.innerHTML='<div class="crusher-grid">'+D.CRUSHERS.map((c,i)=>{
  const pct=c.st==='running'?Math.round(c.tph/c.rated*100):0;
  const led=c.st==='running'?'g':c.st==='maint'?'a':c.alrt?'r':'';
  return '<div class="crusher" data-i="'+i+'"><h4><span class="led '+led+'"></span>'+esc(c.n)+'</h4>'+
  '<div class="row"><span>Status</span><b>'+c.st.toUpperCase()+'</b></div>'+
  '<div class="row"><span>Product</span><b>'+c.prod+' · '+c.tph+' t/h</b></div>'+
  '<div class="progressbar '+(pct>80?'g':pct>50?'a':'r')+'" style="margin:4px 0"><i style="width:'+pct+'%"></i></div>'+
  '<div class="row"><span>Rated utilisation</span><b>'+pct+'%</b></div>'+
  '<div class="row"><span>Operator</span><b>'+c.op+'</b></div>'+
  '<div class="row"><span>Running hours</span><b>'+c.hrs.toLocaleString()+' h</b></div>'+
  '<div class="row"><span>Bearing temp</span><b style="color:'+(c.tmp>90?'var(--red)':c.tmp>75?'var(--amber)':'var(--green)')+'">'+c.tmp+' °C</b></div>'+
  '<div class="row"><span>Alerts</span><b>'+(c.alrt?'<span class="badge red">'+c.alrt+' open</span>':'<span class="badge green">None</span>')+'</b></div>'+
  '<div class="row"><span>Next maintenance</span><b>'+(c.mnt?c.mnt+' service-hrs':'IN PROGRESS — WO-7882')+'</b></div></div>';}).join('')+'</div>';
 el.querySelectorAll('.crusher').forEach(card=>card.onclick=()=>{const c=D.CRUSHERS[+card.dataset.i];NWX.modal('Crusher — '+c.n,
  '<div class="statgrid">'+[['Product',c.prod],['Output',c.tph+' t/h'],['Rated',c.rated+' t/h'],['Operator',c.op],['Hours',c.hrs.toLocaleString()],['Temp',c.tmp+' °C'],['MTBF','21.4 days'],['Next PM',(c.mnt||'due')+' h']].map(s=>'<div class="statmini"><span>'+s[0]+'</span><b>'+s[1]+'</b></div>').join('')+'</div>'+
  '<div class="ck-body" id="crsub" style="height:230px;margin-top:10px"></div>',
  function(b){Charts.render(b.querySelector('#crsub'),{type:'line',title:'Telemetry',labels:D.daysLabels(14),series:[{name:'t/h',data:D.ts(14,c.tph||c.rated*.5,.08,0,'tr'+i),color:C[0]},{name:'Temp °C/2',data:D.ts(14,c.tmp/2,.05,0,'tt'+i),color:C[6],dash:true}],fmt:'num'});});});}
 render(); NWX.addLive(()=>{D.CRUSHERS.forEach(c=>{if(c.st==='running'){c.tph=Math.max(120,Math.min(c.rated,c.tph+Math.round((Math.random()-0.48)*9)));c.tmp=Math.max(55,Math.min(97,c.tmp+Math.round((Math.random()-0.5)*3)));}});render();}); };

/* ---------------- Weighbridge live queue ---------------- */
if(!window.WQ){ window.WQ={ wait:[['TRK-1201','S. Boateng','GH Karim & Partners','Quarry Dust'],['TRK-1601','B. Ahenkorah','AshGold','Rockfill'],['TRK-1502','E. Kufuor','Tema Port Exp.','Aggregate 20mm'],['TRK-1260','D. Mensah','Kpone LP','Aggregate 10mm']],
  weigh:[['TRK-1042','A. Lamptey','Volta Infra','Aggregate 20mm',44.8,16.9]], done:[['TRK-1187','K. Agyeman','Adenta Dev','Aggregate 14mm',27.1,'Paid'],['TRK-1588','G. Wiredu','KMA / AshGold','Laterite',26.4,'Credit'],['TRK-1455','P. Ansah','Min. of Roads','Aggregate 14mm',28.2,'Credit'],['TRK-1319','Y. Oduro','Tema Port','Plant transfer',31.6,'Internal']] };}
MOUNT.wqueue=function(el){ function render(){ const W=window.WQ;
  const col=(t,cls,rows,fmt)=>'<div class="kb-col" style="border-style:solid"><h6>'+t+' <span>'+rows.length+'</span></h6>'+rows.map(r=>'<div class="kb-card '+cls+'">'+fmt(r)+'</div>').join('')+'</div>';
  const w=new Date().toTimeString().slice(0,5);
  el.innerHTML='<div class="hlist" style="display:flex;gap:16px;flex-wrap:wrap;padding-bottom:0">'+
    '<span class="chip"><span class="dot" style="background:var(--green)"></span>Bridge WB-1 live</span><span class="chip"><span class="dot" style="background:var(--amber)"></span>WB-2 queueing</span>'+
    '<span class="chip on">'+D.LIVE.weighed+' weighed today</span><span class="chip">Last ticket '+w+'</span></div>'+
   '<div class="kanban" style="grid-template-columns:1fr 1fr 1.4fr">'+
    col('Waiting — entry control','',W.wait,r=>'<b>'+r[0]+'</b> · '+esc(r[1])+'<div style="color:var(--muted);font-size:11px">'+esc(r[2])+'<br>'+esc(r[3])+'</div>')+
    col('On platform — weighing','',W.weigh,r=>'<b>'+r[0]+'</b> · '+esc(r[1])+'<div style="font-variant-numeric:tabular-nums;margin-top:4px">Gross <b>'+r[4]+' t</b> → Tare '+r[5]+' t<br>Net <b>'+(r[4]-r[5]).toFixed(1)+' t</b></div><div style="color:var(--muted);font-size:11px;margin-top:3px">'+esc(r[2])+' — '+esc(r[3])+'</div>')+
    col('Cleared — last hour','',W.done,r=>'<b>'+r[0]+'</b> · '+esc(r[1])+' · '+r[4]+' t <span class="badge '+(r[5]==='Paid'?'green':r[5]==='Credit'?'amber':'blue')+'">'+r[5]+'</span><div style="color:var(--muted);font-size:11px;margin-top:2px">'+esc(r[2])+' · '+esc(r[3])+'</div>')+
   '</div>'; }
 render();
 NWX.addLive(()=>{ const W=window.WQ; if(Math.random()<0.65&&W.wait.length){
   const v=W.wait.shift(); const done=v[3]? W.weigh.map(x=>x):null;
   if(W.weigh.length){const w=W.weigh.shift(); w[4]=w[4]-w[5]+''; W.done.unshift([w[0],w[1],w[2],w[3],(Math.round((+w[4])*10)/10),Math.random()>0.4?'Paid':'Credit']); if(W.done.length>6)W.done.pop();}
   W.weigh.push([v[0],v[1],v[2],v[3],Math.round((40+Math.random()*6)*10)/10,Math.round((16+Math.random()*2)*10)/10]);
   W.wait.push(['TRK-'+(1100+Math.floor(Math.random()*600)),['A. Mensah','K. Osei','D. Boakye','F. Appiah'][Math.floor(Math.random()*4)],D.CLIENTS[Math.floor(Math.random()*10)],D.PRODUCTS[Math.floor(Math.random()*6)].n]);
   D.LIVE.weighed++; D.LIVE.soldToday+=27; render(); if(NWX.state.mod==='weigh')NWX.refreshKpis();
 }});};

/* ---------------- Stockpile visual yard ---------------- */
MOUNT.pilemap=function(el){
 el.innerHTML='<div class="hlist" style="display:flex;gap:8px;flex-wrap:wrap;padding-bottom:2px">'+
  ['Pile A1','Pile A2','Pile A3','ROM Pad'].map(p=>'<span class="chip">'+p+'</span>').join('')+'<span class="chip"><span class="dot" style="background:var(--red)"></span>3 alerts — drone survey 06 Aug verified</span></div>'+
 '<div class="piles">'+D.PILES.map((p,i)=>{const pct=Math.round(p.qty/p.cap*100);const st=pct>100?'red':pct<25?'amber':'green';
  return '<div class="pile '+(p.alert?'warn':'')+'" data-i="'+i+'"><div class="cone"><i></i></div>'+
   '<div style="font-weight:700;font-size:12.5px">'+p.n+'</div>'+
   '<div style="font-size:11px;color:var(--muted)">'+p.qry+' · Grade '+p.q+' · surveyed '+p.surv+'</div>'+
   '<div class="cap"><i class="'+(pct>100?'warn':'')+'" style="width:'+Math.min(100,pct)+'%"></i></div>'+
   '<div style="display:flex;justify-content:space-between;font-size:11px"><b>'+p.qty.toLocaleString()+' t</b><span style="color:var(--muted)">'+(pct>100?pct+'% OVER':pct+'% of '+p.cap.toLocaleString()+'t')+'</span></div>'+
   (p.alert?'<div style="margin-top:5px"><span class="badge '+st+'">'+(pct>100?'Overstock':pct<25?'Below reorder':'Check')+'</span></div>':'')+'</div>';}).join('')+'</div>';
 el.querySelectorAll('.pile').forEach(pl=>pl.onclick=()=>{const p=D.PILES[+pl.dataset.i];
  const rows=[['Date','Movement','In (t)','Out (t)','Balance (t)']];let bal=p.qty;const r=D.mulberry(D.hseed(p.n));
  for(let i2=0;i2<7;i2++){const d2='2026-08-0'+(9-i2);const inn=Math.round(600+r()*2400),out=Math.round(500+r()*2200);
   rows.unshift([d2,i2%2?'Sales dispatch':'Crusher output',inn,i2%2?out:0,bal]);bal=bal-inn+(i2%2?out:0);}
  rows.push(['','','','','']);
  NWX.drill({title:'Stockpile ledger — '+p.n,label:p.prod+' · '+p.q,customRows:rows,fmt:'t'});});};

/* ---------------- Portfolio Gantt ---------------- */
MOUNT.gantt=function(el){ Charts.render(el,{type:'gantt',title:'Portfolio Gantt',fmt:'num',legend:false,rows:D.PROJECTS.map(p=>({name:p.id+' '+p.n,ps:p.sd,pe:p.pd,as:p.sd,ae:p.fd,prog:p.comp,status:p.st==='done'?'done':(p.risk==='high'?'delayed':'ok')}))});
 el.insertAdjacentHTML('beforeend','<div class="hlist" style="display:flex;gap:12px;flex-wrap:wrap;font-size:11px;color:var(--muted)"><span><span class="badge grey">Plan</span> baseline bar</span><span><span class="badge blue">Actual</span> forecast bar — drag to re-baseline (logged)</span><span>Red line = today</span></div>');};

/* ---------------- Individual project sheet ---------------- */
MOUNT.pdetail=function(el){ let cur='P-2210';
 function render(){ const p=D.PROJECTS.find(x=>x.id===cur);
  const eac=p.cost+(p.bud-p.cost), gp=p.cert-p.cost;
  el.innerHTML='<div class="hlist" style="display:flex;gap:7px;flex-wrap:wrap">'+D.PROJECTS.map(x=>'<span class="chip '+(x.id===cur?'on':'')+'" data-p="'+x.id+'">'+x.id+'</span>').join('')+'</div>'+
   '<div class="statgrid">'+[['Client',p.client],['Contract value',fm(p.cv)],['Revised value',fm(p.rc)],['Start',p.sd],['Planned finish',p.pd],['Forecast finish',p.fd],['Completion',p.comp+'%'],['PM',p.pm],['Approved budget',fm(p.bud)],['Actual cost',fm(p.cost)],['Committed',fm(p.comm)],['Cost to complete',fm(Math.max(0,p.bud-p.cost-p.comm))],['EAC',fm(eac)],['Revenue recognised',fm(p.cert*0.98)],['Gross profit',fm(gp)],['Certified',fm(p.cert)],['Collected',fm(p.coll)],['Retention o/s',fm(p.ret)],['Variations',fm(p.var)],['Claims pending',p.var?'1':'0'],['Safety incidents',p.id==='P-2210'?3:1],['Status',p.st]].map(s=>'<div class="statmini"><span>'+s[0]+'</span><b style="font-size:13px">'+s[1]+'</b></div>').join('')+'</div>'+
   '<div class="grid" style="margin:6px 10px 10px"><div class="panel" style="grid-column:span 7"><div class="panel-h"><h3>'+p.id+' S-Curve — planned vs actual</h3></div><div class="panel-b" id="pdsc"></div></div>'+
   '<div class="panel" style="grid-column:span 5"><div class="panel-h"><h3>Cost breakdown</h3></div><div class="panel-b" id="pdcb"></div></div></div>';
  el.querySelectorAll('.chip').forEach(ch=>ch.onclick=()=>{cur=ch.dataset.p;render();});
  const st=new Date(p.sd).getTime(),en=new Date(p.fd).getTime(),now=Math.min(Date.now(),en);
  const labels=[],pl=[],ac=[];const n=10;
  for(let i=0;i<=n;i++){const t=st+(en-st)*i/n;labels.push(new Date(t).toLocaleDateString('en-GB',{month:'short',yy:'2-digit'}).replace(' ','-'));
   pl.push(Math.round(100/(1+Math.exp(-6*(i/n-0.5)))*10)/10);
   const e=Math.max(0,Math.min(1,(t-st)/(now-st))); ac.push(t>now?null:Math.round(pl[i]*(p.comp/(100*Math.max(0.01,Math.min(1,(now-st)/(en-st)))))*10)/10);}
  Charts.render(el.querySelector('#pdsc'),{type:'line',title:'S-curve',labels:labels,series:[{name:'Planned %',data:pl,color:C[8],dash:true},{name:'Actual %',data:ac,color:C[0],area:true}],fmt:'pct',target:{value:p.comp,label:'Today'}});
  Charts.render(el.querySelector('#pdcb'),{type:'doughnut',title:'Cost',items:[{label:'Materials',value:Math.round(p.cost*.34),color:C[0]},{label:'Labour',value:Math.round(p.cost*.21),color:C[1]},{label:'Plant & fuel',value:Math.round(p.cost*.24),color:C[4]},{label:'Subcontract',value:Math.round(p.cost*.14),color:C[3]},{label:'Prelims',value:Math.round(p.cost*.07),color:C[5]}],fmt:'money',center:''});}
 render();};

/* ---------------- Equipment cards ---------------- */
MOUNT.eqcards=function(el){ const ico={'CAT 345 Excavator':'exc','Volvo EC480 Excavator':'exc','Hitachi ZX350 Excavator':'exc'};
 el.innerHTML='<div class="crusher-grid">'+D.EQUIPMENT.map((e,i)=>{const st=e[3];
  return '<div class="crusher" data-i="'+i+'"><h4><span class="led '+(st==='running'?'g':st==='maint'?'a':st==='down'?'r':'')+'"></span>'+e[0]+'</h4>'+
  '<div style="font-size:11.5px;color:var(--muted);margin-bottom:5px">'+e[1]+'</div>'+
  '<div class="row"><span>Location</span><b>'+e[2]+'</b></div><div class="row"><span>Operator</span><b>'+e[6]+'</b></div>'+
  '<div class="row"><span>Status</span><b>'+st.toUpperCase()+'</b></div><div class="row"><span>Hours</span><b>'+e[4].toLocaleString()+'</b></div>'+
  '<div class="row"><span>Fuel</span></div><div class="progressbar '+(e[5]>50?'g':e[5]>25?'a':'r')+'"><i style="width:'+e[5]+'%"></i></div>'+
  '<div class="row" style="margin-top:4px"><span>Next service</span><b>'+e[7]+' h</b></div>'+
  '<div class="row"><span>Alert</span><b>'+(st==='down'?'<span class="badge red">Breakdown</span>':st==='maint'?'<span class="badge amber">In workshop</span>':'<span class="badge green">None</span>')+'</b></div></div>';}).join('')+'</div>';
 el.querySelectorAll('.crusher').forEach(c=>c.onclick=()=>NWX.entity('equipment',{label:D.EQUIPMENT[+c.dataset.i][0]+' — '+D.EQUIPMENT[+c.dataset.i][1]}));};

/* ---------------- Kanban (maintenance) ---------------- */
MOUNT.kanban=function(el){ const cols=['Backlog','Planned','In progress','Done'];
 const cards=[['WO-7890','CR-902 bearing replacement','Critical','red'],['WO-7884','TRK-1407 clutch','High','amber'],['WO-7875','EX-052 500h service','PM','blue'],['WO-7871','CV-4 belt re-track','Medium','blue'],['WO-7860','LD-118 pins & bushes','Medium','blue'],['WO-7855','QJ341 jaw plates','Planned','grey'],['WO-7832','Genset load test','Done','green'],['WO-7828','GR-077 circle gearbox','Done','green']];
 const dist={Backlog:cards.slice(0,2),Planned:cards.slice(2,4),'In progress':cards.slice(4,6),Done:cards.slice(6,8)};
 el.innerHTML='<div class="kanban">'+cols.map(c=>'<div class="kb-col" data-c="'+c+'"><h6>'+c+' <span class="cnt">'+dist[c].length+'</span></h6>'+dist[c].map(k=>'<div class="kb-card" draggable="true" data-id="'+k[0]+'"><b>'+k[0]+'</b> <span class="badge '+k[3]+'">'+k[2]+'</span><div style="color:var(--muted);font-size:11px;margin-top:3px">'+k[1]+'</div></div>').join('')+'</div>').join('')+'</div>';
 let dragEl=null;
 el.querySelectorAll('.kb-card[draggable]').forEach(card=>{ card.addEventListener('dragstart',()=>{dragEl=card;setTimeout(()=>card.classList.add('dragover'),0);});
  card.addEventListener('dragend',()=>{card.classList.remove('dragover');el.querySelectorAll('.kb-col').forEach(c2=>c2.classList.remove('over'));});});
 el.querySelectorAll('.kb-col').forEach(col=>{ col.addEventListener('dragover',e=>{e.preventDefault();col.classList.add('over');});
  col.addEventListener('dragleave',()=>col.classList.remove('over'));
  col.addEventListener('drop',e=>{e.preventDefault();col.classList.remove('over');if(dragEl){col.appendChild(dragEl);
   el.querySelectorAll('.kb-col').forEach(c2=>c2.querySelector('.cnt').textContent=c2.querySelectorAll('.kb-card').length);
   NWX.toast('Work order moved','WO '+dragEl.dataset.id+' → '+col.dataset.c,'green'); NWX.audit('Work order re-staged',dragEl.dataset.id+' → '+col.dataset.c);}});});};

/* ---------------- Safety site map ---------------- */
MOUNT.safemap=function(el){ const zones=[['Blast exclusion — 500 m',6,8,34,30],['Crusher hall',44,10,20,18],['ROM pad',66,8,26,22],['Fuel farm',8,44,16,16],['Magazine',28,46,14,14],['Workshops',46,42,20,18],['Haul road A',6,66,86,10],['Offices & muster',70,40,22,18]];
 const mk=[['i',16,18,'Near-miss INC-26-0412 — LV in exclusion zone','red'],['i',52,16,'First aid INC-26-0418 — pinch point','amber'],['p',56,26,'Permit-to-work: hot work #PW-118','blue'],['r',14,48,'Restricted — fuel farm','grey'],['r',32,50,'Restricted — explosives magazine','grey'],['e',76,46,'Muster point 1 (120 cap.)','green'],['e',12,34,'First-aid station','green'],['e',60,52,'Fire point FE-09','amber'],['p',84,14,'Permit: confined space #PW-121','blue'],['i',38,70,'Speeding flag — haul road','amber']];
 el.innerHTML='<div class="hlist" style="display:flex;gap:7px;flex-wrap:wrap;padding-bottom:2px">'+[['i','Incidents','red'],['p','Active permits','blue'],['r','Restricted','grey'],['e','Emergency points','green']].map(l=>'<span class="chip on" data-l="'+l[0]+'"><span class="dot" style="background:var(--'+l[2]+')"></span>'+l[1]+'</span>').join('')+'</div>'+
 '<div class="plan">'+zones.map(z=>'<div class="zone" style="left:'+z[1]+'%;top:'+z[2]+'%;width:'+z[3]+'%;height:'+z[4]+'%">'+z[0]+'</div>').join('')+
  mk.map((m,i)=>'<div class="mk ms-'+m[0]+' '+(m[0]==='i'?'pulse':'')+'" data-i="'+i+'" style="left:'+m[1]+'%;top:'+m[2]+'%;background:var(--'+m[4]+')">'+m[0].toUpperCase()+'</div>').join('')+'</div>';
 el.querySelectorAll('.chip').forEach(ch=>ch.onclick=()=>{ch.classList.toggle('on');el.querySelectorAll('.ms-'+ch.dataset.l).forEach(m=>m.style.display=ch.classList.contains('on')?'grid':'none');});
 el.querySelectorAll('.mk').forEach(m2=>m2.onclick=()=>NWX.modal('Map point',('<p style="margin:0 0 8px">'+esc(mk[+m2.dataset.i][3])+'</p><div class="hrow"><span class="nm">Area</span><b>'+esc(zones.find(z=>{const mm=mk[+m2.dataset.i];return mm[1]>=z[1]&&mm[1]<=z[1]+z[3]&&mm[2]>=z[2]&&mm[2]<=z[2]+z[4];})? zones.find(z=>{const mm=mk[+m2.dataset.i];return mm[1]>=z[1]&&mm[1]<=z[1]+z[3]&&mm[2]>=z[2]&&mm[2]<=z[2]+z[4];})[0]:'Open area')+'</b></div>'),null,'OK'));};

/* ---------------- Approval queue ---------------- */
if(!window.NWAPPROVE){ const r=D.mulberry(505); const types=['Purchase Requisition','Purchase Order','Supplier Invoice','Payment Voucher','Budget Request','Journal Entry','Project Variation','Payment Certificate','Equipment Transfer','Asset Disposal','Leave Request','Payroll Run','Tax Return','Safety Corrective Action'];
 window.NWAPPROVE=types.map((t,i)=>({id:'AP-'+(7712-i),type:t,ref:['PR-26-0441','PO-26-1312','INV-OG-8841','PV-26-0771','BT-26-044','JV-26-0912','VO-P2210-14','IPC-2026-141','ET-118','AD-009','LV-2210','PRY-2026-08','VAT-JUL-26','CA-CN-331'][i],by:['E. Osei','A. Mensah','K. Boateng','R. Asante','D. Amoah','S. Adjei'][i%6],amt:Math.round((20+r()*900)*1e3),sla:(i%5)-1.5,st:'Pending'}));}
const WFMAP={'Purchase Requisition':['proc','Procure-to-pay (P2P)'],'Purchase Order':['proc','Procure-to-pay (P2P)'],'Supplier Invoice':['proc','Procure-to-pay (P2P)'],'Payment Voucher':['treasury','Payment run'],'Budget Request':['budget','Budget virement'],'Journal Entry':['finance','Journal approval'],'Project Variation':['projects','Variation order'],'Payment Certificate':['ipc','Interim payment certificate'],'Equipment Transfer':['equip','Equipment inter-site transfer'],'Asset Disposal':['finance','Journal approval'],'Leave Request':['hr','Hire to retire — onboarding'],'Payroll Run':['hr','Monthly payroll run'],'Tax Return':['tax','Statutory filing (VAT / WHT / PAYE / SSNIT)'],'Safety Corrective Action':['hse','Incident investigation']};
MOUNT.apprq=function(el){ const ro=NWX.state.role&&NWX.state.role.ro;
 function render(){ const rows=window.NWAPPROVE.filter(a=>a.st==='Pending');
  el.innerHTML='<div class="hlist" style="display:flex;gap:8px;flex-wrap:wrap;padding-bottom:6px"><span class="chip on">'+rows.length+' pending</span><span class="chip">'+window.NWAPPROVE.filter(a=>a.st==='Rejected').length+' rejected</span><button class="btn sm ghost" id="allappr">Approve all mine</button>'+(NWX.state.offline?'<span class="chip" style="border-color:var(--amber);color:var(--amber)">Offline — approvals suspended until sync</span>':'')+'</div>'+
  (rows.length?'':'<div class="empty-state">✓ Inbox zero — nothing waiting on you.</div>')+
  rows.slice(0,14).map((a,i)=>'<div class="alertcard" data-i="'+i+'" style="margin:0 12px 8px"><h5><span class="badge blue">'+a.type+'</span> '+a.ref+' <span style="color:var(--muted);font-weight:400">· '+a.id+'</span></h5>'+
   '<div class="meta">Requested by '+a.by+' · Amount <b>'+fm(a.amt)+'</b> · SLA '+(a.sla<0?'<span style="color:var(--red)">'+Math.abs(a.sla).toFixed(0)+'d overdue</span>':a.sla.toFixed(0)+'d left')+' · Workflow: Requester → '+NWX.state.roleName+' → (next: '+['CFO','MD','Board'][i%3]+') <span class="chip" style="font-size:9.5px;padding:1px 8px">⛓ '+(WFMAP[a.type]||['exec','Exception escalation'])[1]+'</span></div>'+
   '<div class="acts"><button class="btn sm pri" data-x="ok">✓ Approve</button><button class="btn sm" data-x="no">✕ Reject</button><button class="btn sm ghost" data-x="fw">Forward</button><button class="btn sm ghost" data-x="vw">View document</button></div></div>').join('');
  el.querySelectorAll('.alertcard').forEach(card=>card.querySelectorAll('button').forEach(b=>b.onclick=e=>{e.stopPropagation();
    const a=rows[+card.dataset.i]; const x=b.dataset.x;
    if(x==='vw'){NWX.modal('Document preview — '+a.ref,'<div class="empty-state">Embedded DMS viewer<br><small>'+a.type+' '+a.ref+' · signed PDF · 3 pages · audit-anchored</small></div>',null,'Close');return;}
    if(x==='fw'){NWX.toast('Forwarded','Routed to next approver with your comment template.','blue');return;}
    if(NWX.state.offline){NWX.toast('Offline mode','Approvals are blocked offline — request queued for review, not execution.','amber');return;}
    if(ro){NWX.toast('Read-only role','Your role ('+NWX.state.roleName+') cannot execute approvals.','amber');return;}
    a.st=x==='ok'?'Approved':'Rejected'; card.remove();
    NWX.toast(x==='ok'?'Approved':'Rejected',a.type+' '+a.ref+(x==='ok'?' — posted & notification sent (in-app, SMS, email).':' — returned with reasons.'),x==='ok'?'green':'red');
    NWX.audit((x==='ok'?'Approval completed':'Approval rejected'),a.id+' '+a.type); NWX.bumpBadges();
    render();}));
  const all=el.querySelector('#allappr'); if(all)all.onclick=()=>{ if(NWX.state.offline){NWX.toast('Offline','Bulk approval blocked while offline.','amber');return;} window.NWAPPROVE.forEach(a=>{if(a.st==='Pending'&&a.amt<8e5)a.st='Approved';}); NWX.toast('Bulk approve','All pending items within your delegated limit (₵800k) approved & logged.','green'); NWX.audit('Bulk approval (within delegated limit)'); NWX.bumpBadges(); render(); };}
 render();};

/* ---------------- AI recommendations ---------------- */
MOUNT.airecs=function(el){ let flt='All';
 function render(){ const doms=['All','Production','Fuel','Maintenance','Inventory','Collections','Treasury','Assets','Projects','HSE','Tax','Safety Risk'];
  el.innerHTML='<div class="hlist" style="display:flex;gap:6px;flex-wrap:wrap;padding-bottom:6px">'+doms.map(d2=>'<span class="chip '+(d2===flt?'on':'')+'" data-d="'+d2+'">'+d2+'</span>').join('')+'</div>'+
  D.AI_RECS.filter(r2=>flt==='All'||r2[0]===flt).map((r2,i)=>{
   const real=D.AI_RECS.indexOf(r2); if(window.AIR&&window.AIR[real])return '';
   return '<div class="reco" data-i="'+real+'" style="margin:0 12px 9px"><h5><span class="badge '+(r2[3]==='critical'?'red':r2[3]==='high'?'amber':'blue')+'">'+r2[3].toUpperCase()+'</span> '+esc(r2[1].split('—')[0])+'</h5>'+
   '<p>'+esc(r2[1])+'</p><p style="color:var(--text)"><b>Recommended:</b> '+esc(r2[2])+'</p>'+
   '<div class="conf">Model confidence <span class="confbar"><i style="width:'+r2[4]+'%"></i></span>'+r2[4]+'% · advisory only — human review required</div>'+
   '<div class="acts"><button class="btn sm pri" data-x="acc">Accept → create task</button><button class="btn sm" data-x="sno">Snooze 7d</button><button class="btn sm ghost" data-x="why">Why this?</button><button class="btn sm ghost danger" data-x="rej">Dismiss</button></div></div>';}).join('');
  el.querySelectorAll('.chip[data-d]').forEach(ch=>ch.onclick=()=>{flt=ch.dataset.d;render();});
  el.querySelectorAll('.reco').forEach(rc=>rc.querySelectorAll('button').forEach(b=>b.onclick=()=>{ const i=+rc.dataset.i;const x=b.dataset.x;window.AIR=window.AIR||{};
   if(x==='why'){NWX.modal('Model explanation','<p><b>Drivers (SHAP-style, top 3):</b></p><div class="hrow"><span class="nm">Trend slope 21d</span><div class="bar"><i style="width:82%;background:var(--blue)"></i></div><span class="vv">+0.41</span></div><div class="hrow"><span class="nm">Peer benchmark gap</span><div class="bar"><i style="width:64%;background:var(--blue)"></i></div><span class="vv">+0.27</span></div><div class="hrow"><span class="nm">Seasonality (Harmattan)</span><div class="bar"><i style="width:38%;background:var(--blue)"></i></div><span class="vv">+0.14</span></div><p style="color:var(--muted);font-size:12px">Model: see registry below · trained on governed ERP data · decisions remain with management.</p>',null,'Close');return;}
   if(x==='acc'){NWX.toast('Task created','Recommendation accepted — task assigned to owner with due date + audit entry.','green');NWX.audit('AI recommendation accepted','# '+(i+1));}
   else if(x==='sno'){NWX.toast('Snoozed','Will resurface on 17 Aug unless indicators worsen.','blue');}
   else {NWX.toast('Dismissed','Feedback recorded to retrain ranking.','amber');}
   window.AIR[i]=true; rc.remove(); NWX.audit('AI recommendation '+(x==='rej'?'rejected':x==='sno'?'snoozed':'viewed/acted'),'# '+(i+1));}));  }
 render();};

/* ---------------- Report catalogue ---------------- */
MOUNT.repcat=function(el,sec,mod){ const REP=(mod&&mod.catalogue)||(window._mod&&window._mod.catalogue)||[];
 el.innerHTML='<div class="hlist" style="padding-bottom:6px"><input type="search" id="repq" placeholder="Search '+REP.length+' governed reports…" style="flex:1;max-width:340px;background:var(--inset);border:1px solid var(--border);color:var(--text);border-radius:8px;padding:8px 11px;font-size:12.5px"></div>'+
 '<div class="tiles" id="repgrid" style="grid-template-columns:repeat(auto-fill,minmax(210px,1fr))">'+REP.map(r=>'<div class="tile" data-r="'+r[0]+'" style="align-items:flex-start;text-align:left"><div style="display:flex;gap:8px;align-items:center">'+NWX.icon('report')+'<b>'+r[0]+'</b></div><span style="color:var(--muted);font-weight:400;font-size:11px">'+r[1]+'</span><span style="color:var(--faint);font-size:10px">Module: '+r[2]+' · row-level security applies</span></div>').join('')+'</div>';
 el.querySelector('#repq').oninput=e=>{const q=e.target.value.toLowerCase();el.querySelectorAll('.tile').forEach(t=>t.style.display=t.dataset.r.toLowerCase().includes(q)?'':'none');};
 el.querySelectorAll('.tile').forEach(t=>t.onclick=()=>NWX.runReport(t.dataset.r)); };

/* ---------------- Financial statements ---------------- */
MOUNT.finstate=function(el){ const f1=f();const M2=n=>Math.round(n*f1);
 const pl=[['Revenue',48200],['Cost of sales',-35300],['Gross profit',12900,'t'],['Distribution costs',-1240],['Administrative expenses',-3860],['Operating profit',7800,'t'],['Finance income',410],['Finance costs',-2260],['Profit before tax',5950,'t'],['Income tax + levies',-650],['Net profit for the period',5300,'tt']];
 const fp=[['Property, plant & equipment',86200],['Right-of-use assets',2400],['Non-current assets',88600,'t'],['Inventories',6900],['Trade & other receivables',17400],['Cash & equivalents',31600],['Current assets',56000,'t'],['Total assets',142600,'tt'],['Trade & other payables',9800],['Borrowings',18400],['Tax liabilities',2940],['Total liabilities',58400*0+31140,'t'],['Share capital',20000],['Retained earnings',61460],['Total equity',81460*0+81460*0+20000+61460,'t']];
 const cf=[['Cash from operations',6200],['Working-capital movements',-2100],['Net operating cash',4100,'t'],['Purchase of PPE',-1800],['Net investing cash',-1800,'t'],['Drawdown net of repayments',1100],['Net financing cash',1100,'t'],['Net increase in cash',3400,'t'],['Opening cash (1 Aug)',27400],['Closing cash',24800*0+27400+3400-6000?21800:21800,'tt']];
 function block(ti,rows,idt){ return '<div class="statement"><header data-b="'+ti+'"><span>▾ '+ti+'</span><span style="display:flex;gap:6px"><button class="btn sm ghost" data-full="'+ti+'">Open full statement →</button></span></header><div class="sb-body" style="display:none">'+
  rows.map(r=>'<div class="sr '+(r[2]==='t'?'tot':'')+'" style="'+(r[2]==='tt'?'border-top:2px solid var(--border-2);font-weight:800':'')+'"><span>'+r[0]+'</span><b>'+fm(M2(r[1])*1000 ? M2(r[1])*1000 : r[1])+'</b></div>').join('')+'</div></div>'; }
 el.innerHTML='<div style="padding:12px">'+block('Statement of Profit or Loss — Aug 2026 (₵)',pl)+block('Statement of Financial Position — as at 10 Aug 2026 (₵)',fp)+block('Statement of Cash Flows — MTD (₵)',cf)+'<div class="sr" style="padding:10px 4px"><span>Trial balance status</span><b><span class="badge green">In balance — 0 difference · 1,208 accounts · last close 08:00</span></b></div></div>';
 el.querySelectorAll('header').forEach(h=>h.onclick=e=>{ if(e.target.dataset.full)return; const b=h.nextElementSibling;b.style.display=b.style.display==='none'?'':'none';});
 el.querySelectorAll('[data-full]').forEach(b=>b.onclick=e=>{e.stopPropagation();NWX.modal(b.dataset.full,'<div class="empty-state">Full interactive statement opens here — 1,208 GL accounts · drill to voucher · IFRS/Ghana note disclosures<br><small>Export: PDF · Excel · iXBRL (GRA-ready)</small></div>',null,'Close');});};

/* ---------------- Tax filing calendar ---------------- */
MOUNT.taxcal=function(el){ const items=[['PAYE return','2026-08-13','₵498,200','filed'],['SSNIT + Tier 2','2026-08-13','₵526,400','filed'],['WHT return','2026-08-15','₵312,100','due-soon'],['VAT + NHIL + GETFund + CHRL','2026-08-31','₵1,423,800','due-soon'],['Mineral royalty — Shai Hills','2026-08-31','₵586,000','due-soon'],['CIT instalment Q3','2026-09-30','₵1,410,000','scheduled'],['GSL return','2026-09-15','₵96,400','scheduled']];
 el.innerHTML='<div style="padding:10px 12px;display:flex;flex-direction:column;gap:8px">'+items.map(x=>'<div class="hrow" style="border:1px solid var(--border);border-radius:9px;padding:10px 12px"><span class="badge '+(x[3]==='filed'?'green':x[3]==='due-soon'?'amber':'blue')+'">'+(x[3]==='filed'?'Filed':x[3]==='due-soon'?'Due soon':'Scheduled')+'</span><b style="flex:1">'+x[0]+'</b><span class="vv" style="width:110px;text-align:left">'+x[1]+'</span><b style="width:110px;text-align:right">'+x[2]+'</b>'+(x[3]!=='filed'?'<button class="btn sm pri" data-x="'+x[0]+'">File & pay</button>':'<button class="btn sm ghost" data-r="'+x[0]+'">Receipt</button>')+'</div>').join('')+'</div>';
 el.querySelectorAll('[data-x]').forEach(b=>b.onclick=()=>NWX.modal('File '+b.dataset.x,'<p>Validate against general ledger and GRA portal, then submit via certified integration.</p><div class="hrow"><span class="nm">Ledger check</span><div class="bar"><i style="width:100%;background:var(--green)"></i></div><span class="vv">✓ 0 diff</span></div><div class="hrow"><span class="nm">E-invoice coverage</span><div class="bar"><i style="width:97%;background:var(--green)"></i></div><span class="vv">97.2%</span></div>',null,'Validate & submit to GRA'));
 el.querySelectorAll('[data-r]').forEach(b=>b.onclick=()=>NWX.toast('Receipt','GRA e-receipt '+b.dataset.r+' downloaded to DMS.','green'));};

/* ---------------- Security console ---------------- */
MOUNT.secset=function(el){ const S=NWX.state.sec||{mfa:1,audit:1};
 const rows=[['mfa','Multi-factor authentication','TOTP / hardware key for all privileged roles'],['dev','Device authorization','New devices require admin approval'],['ip','IP allow-listing','Restrict HQ finance to office IPs + VPN'],['to','Session timeout — 30 min idle','Auto sign-out + re-auth on sensitivity elevation'],['mask','Data masking for read-only roles','Amounts blurred for masked roles, watermark on'],['wm','Screen watermark','Diagonal "NOWILLS • CONFIDENTIAL • user id" overlay'],['dl','Download restrictions','Exports logged, watermarked, rate-limited'],['audit','Immutable hash-chained audit log','SHA-256 anchored, append-only storage']];
 el.innerHTML='<div style="padding:4px 14px 12px">'+rows.map(r=>'<div class="setrow"><div class="si"><b>'+r[0].toUpperCase()+' — '+r[1]+'</b><small>'+r[2]+'</small></div><div class="toggle '+(S[r[0]]?'on':'')+'" data-k="'+r[0]+'"></div><span class="badge '+(S[r[0]]?'green':'grey')+'">'+(S[r[0]]?'ON':'OFF')+'</span></div>').join('')+
 '<div class="setrow"><div class="si"><b>Encryption</b><small>TLS 1.3 in transit · AES-256 at rest · field-level for payroll</small></div><span class="badge green">ACTIVE</span></div></div>';
 el.querySelectorAll('.toggle').forEach(t=>t.onclick=()=>{const k=t.dataset.k;NWX.setSec(k,!NWX.state.sec[k]);MOUNT.secset(el);});};

window.__MNT=1;
})();
