`).join("");
// feed
const evs=Object.entries(state.events).filter(([,e])=>e).sort((a,b)=>a[0]x[1]);
$("fEmpty").style.display=evs.length?"none":"block";
$("feed").innerHTML=evs.map(e=>{
const p=esc(e.page||"/"),d=esc(e.d),place=esc([e.city,e.cc].filter(Boolean).join(", ")||"somewhere");
const txt={arrive:`${flag(e.cc)} New visitor from ${place} via ${d}`,pageview:`${flag(e.cc)} Viewed ${p}`,click:`Clicked “${d}” on ${p}`,
contact:`Reached out: ${d}`,outbound:`Left for ${d}`,scroll:`Scrolled to ${d} of ${p}`,form:`Submitted form ${d} on ${p}`}[e.type]||esc(e.type);
return `
${txt}
`}).join("");
// breakdowns
const pages={},ctry={},dev={},src={};
today.forEach(s=>{
for(const k in (s.pg||{}))pages[k]=(pages[k]||0)+s.pg[k];
const c=(s.country?flag(s.cc)+" "+s.country:"Unknown");ctry[c]=(ctry[c]||0)+1;
const d=(s.dev||"?")+" · "+(s.br||"?");dev[d]=(dev[d]||0)+1;
const r=s.ref||"direct";src[r]=(src[r]||0)+1;
});
bars($("sPages"),pages,dec);bars($("sCountries"),ctry);bars($("sDevices"),dev);bars($("sSources"),src);
}
setInterval(render,1000);
/* ---- sparkline: online visitors, last 30 min ---- */
function sample(){history.push(Object.values(state.sessions).filter(isLive).length);if(history.length>360)history.shift();spark()}
function spark(){
const c=$("spark"),dpr=devicePixelRatio||1,w=c.clientWidth,h=c.clientHeight;if(!w)return;
c.width=w*dpr;c.height=h*dpr;const x=c.getContext("2d");x.scale(dpr,dpr);
const n=360,max=Math.max(3,...history),step=w/(n-1),off=n-history.length;
x.strokeStyle="#2A3550";x.lineWidth=1;for(let i=1;i{const px=(off+i)*step,py=h-(v/max)*(h-8)-2;if(i)x.lineTo(px,h-(history[i-1]/max)*(h-8)-2);x.lineTo(px,py)});
x.lineTo(w,h);x.closePath();x.fillStyle="rgba(15,169,122,.28)";x.fill();
x.strokeStyle="#2FE0A8";x.lineWidth=2;x.beginPath();
history.forEach((v,i)=>{const px=(off+i)*step,py=h-(v/max)*(h-8)-2;i?x.lineTo(px,py):x.moveTo(px,py)});x.stroke();
}
setInterval(sample,5000);addEventListener("resize",spark);
/* ---- demo mode (fake visitors so you can see how it looks) ---- */
function demo(){
closeAll();mode="demo";setStatus("demo","Demo data");
const places=[["PH","Philippines","Manila"],["SA","Saudi Arabia","Riyadh"],["AE","United Arab Emirates","Dubai"],["SA","Saudi Arabia","Jeddah"],["US","United States","Austin"],["IN","India","Mumbai"],["EG","Egypt","Cairo"]];
const pages=["/","/services","/portfolio","/contact","/about","/email-marketing"],refs=["direct","google.com","linkedin.com","facebook.com","direct"];
const devs=[["Desktop","Chrome"],["Mobile","Safari"],["Mobile","Chrome"],["Desktop","Edge"]],pick=a=>a[Math.floor(Math.random()*a.length)];
let n=0;const evt=(sid,type,s,d)=>{state.events["e"+String(++n).padStart(6,"0")]={t:Date.now(),sid,type,page:s.page,d,cc:s.cc,city:s.city}};
const spawn=()=>{const [cc,country,city]=pick(places),[dv,br]=pick(devs),page=pick(pages),ref=pick(refs),id="d"+Math.random().toString(36).slice(2,8);
const s=state.sessions[id]={start:Date.now(),last:Date.now(),online:true,vis:"visible",page,pv:1,cc,country,city,dev:dv,br,ref,ret:Math.random()<.3 evt for i="0;i<5;i++){spawn();state.sessions[Object.keys(state.sessions)[i]].start-=Math.random()*240000}" demotimer="setInterval(()=">{
Object.entries(state.sessions).forEach(([id,s])=>{if(!s.online)return;s.last=Date.now();s.vis=Math.random()<.04 r="Math.random();" if k='encodeURIComponent(s.page);s.pg[k]=(s.pg[k]||0)+1;s.scroll=0;evt(id,"pageview",s,"")}' else a quote portfolio xxx render settimeout setup dialog const dlg='$("dlg");' function snippet u='$("dbUrl").value.trim().replace(/\/$/,"");' return tec-lines live tracker>\n