406 lines
16 KiB
HTML
406 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>WEVAL — Agents Command</title>
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@400;600;700&family=JetBrains+Mono:wght@400;700&display=swap');
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
body{background:#020408;overflow:hidden;cursor:crosshair}
|
|
canvas{display:block}
|
|
#tip{position:fixed;pointer-events:none;display:none;z-index:99}
|
|
#tip .box{background:rgba(4,8,20,.92);border:1px solid rgba(6,182,212,.4);border-radius:10px;padding:14px 18px;backdrop-filter:blur(12px);min-width:240px;box-shadow:0 0 40px rgba(6,182,212,.12),inset 0 0 30px rgba(6,182,212,.03)}
|
|
#tip .nm{font-family:'Orbitron',sans-serif;font-size:1rem;color:#fff;font-weight:700;letter-spacing:1px}
|
|
#tip .tp{font-family:'Rajdhani',sans-serif;font-size:.72rem;text-transform:uppercase;letter-spacing:2px;margin:4px 0 8px;padding:2px 8px;display:inline-block;border-radius:4px}
|
|
#tip .ds{font-family:'Rajdhani',sans-serif;color:#8899b8;font-size:.85rem;line-height:1.4;margin-bottom:6px}
|
|
#tip .pr{font-family:'JetBrains Mono',monospace;font-size:.72rem;color:#f59e0b;border-top:1px solid rgba(255,255,255,.06);padding-top:6px;margin-top:4px}
|
|
#tip .bar{height:3px;border-radius:2px;margin-top:8px;background:#111;overflow:hidden}
|
|
#tip .bar i{display:block;height:100%;border-radius:2px;animation:pulse 1.5s ease infinite}
|
|
@keyframes pulse{0%,100%{opacity:.7}50%{opacity:1}}
|
|
#hud{position:fixed;top:0;left:0;right:0;padding:14px 20px;display:flex;justify-content:space-between;align-items:center;z-index:10;background:linear-gradient(180deg,rgba(2,4,8,.95) 0%,transparent 100%);pointer-events:none}
|
|
#hud *{pointer-events:auto}
|
|
.logo{font-family:'Orbitron',sans-serif;font-size:1.1rem;font-weight:900;letter-spacing:3px;color:#06b6d4;text-shadow:0 0 20px rgba(6,182,212,.4)}
|
|
.logo span{color:#a855f7}
|
|
.hud-stats{display:flex;gap:20px}
|
|
.hs{text-align:center}
|
|
.hs-v{font-family:'Orbitron',sans-serif;font-size:1.4rem;font-weight:700;background:linear-gradient(135deg,#06b6d4,#a855f7);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
|
.hs-l{font-family:'Rajdhani',sans-serif;font-size:.6rem;text-transform:uppercase;letter-spacing:2px;color:#4a5a78}
|
|
#bot-hud{position:fixed;bottom:0;left:0;right:0;padding:10px 20px;z-index:10;background:linear-gradient(0deg,rgba(2,4,8,.9) 0%,transparent 100%);pointer-events:none}
|
|
.zones-bar{display:flex;justify-content:center;gap:4px}
|
|
.zb{font-family:'Rajdhani',sans-serif;font-size:.68rem;padding:4px 12px;border-radius:4px;color:#5a6a88;border:1px solid #111828;cursor:pointer;pointer-events:auto;transition:.2s;letter-spacing:1px}
|
|
.zb:hover,.zb.lit{color:#06b6d4;border-color:#06b6d4;background:rgba(6,182,212,.06);text-shadow:0 0 8px rgba(6,182,212,.3)}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="c"></canvas>
|
|
<div id="tip"><div class="box"><div class="nm"></div><div class="tp"></div><div class="ds"></div><div class="pr"></div><div class="bar"><i></i></div></div></div>
|
|
<div id="hud">
|
|
<div class="logo">WEVAL <span>COMMAND</span></div>
|
|
<div class="hud-stats">
|
|
<div class="hs"><div class="hs-v">31</div><div class="hs-l">Agents</div></div>
|
|
<div class="hs"><div class="hs-v">8</div><div class="hs-l">Zones</div></div>
|
|
<div class="hs"><div class="hs-v" id="fps">60</div><div class="hs-l">FPS</div></div>
|
|
</div>
|
|
</div>
|
|
<div id="bot-hud"><div class="zones-bar" id="zbar"></div></div>
|
|
<script>
|
|
const C=document.getElementById('c'),X=C.getContext('2d');
|
|
let W,H,mx=-1,my=-1,hov=null,frame=0,camX=0,camTargetX=0;
|
|
const dpr=Math.min(devicePixelRatio,2);
|
|
|
|
function resize(){W=innerWidth;H=innerHeight;C.width=W*dpr;C.height=H*dpr;X.scale(dpr,dpr)}
|
|
addEventListener('resize',resize);resize();
|
|
|
|
const ZN=[
|
|
{id:'prospect',lbl:'PROSPECTION',icon:'🎯',clr:'#2563eb',x:0},
|
|
{id:'consult',lbl:'CONSULTING',icon:'💼',clr:'#7c3aed',x:0},
|
|
{id:'dev',lbl:'DÉVELOPPEMENT',icon:'⚡',clr:'#10b981',x:0},
|
|
{id:'infra',lbl:'INFRASTRUCTURE',icon:'🏗️',clr:'#f59e0b',x:0},
|
|
{id:'security',lbl:'SÉCURITÉ',icon:'🛡️',clr:'#ef4444',x:0},
|
|
{id:'delivery',lbl:'LIVRAISON',icon:'🚀',clr:'#06b6d4',x:0},
|
|
{id:'pharma',lbl:'PHARMA',icon:'💊',clr:'#d946ef',x:0},
|
|
{id:'monitor',lbl:'MONITORING',icon:'📡',clr:'#eab308',x:0},
|
|
];
|
|
|
|
const AG=[
|
|
{n:'Ethica',e:'💊',z:0,t:'pharma',d:'HCP scraping DabaDoc+LinkedIn',p:'131K+ médecins DZ/MA/TN'},
|
|
{n:'Analyst',e:'🔍',z:0,t:'cognitive',d:'Analyse besoins & requirements',p:'Specs, études marché'},
|
|
{n:'Writer',e:'✍️',z:0,t:'cognitive',d:'Rédaction emails & proposals',p:'Cold emails, articles B2B'},
|
|
{n:'CEO',e:'👔',z:1,t:'autonomous',d:'Agent autonome stratégique',p:'Décisions, budget, hiring'},
|
|
{n:'Architect',e:'🏗️',z:1,t:'cognitive',d:'Architecture technique',p:'Diagrammes, blueprints'},
|
|
{n:'Planner',e:'📋',z:1,t:'cognitive',d:'Roadmaps & milestones',p:'Sprint plans, Gantt'},
|
|
{n:'DeerFlow',e:'🦌',z:1,t:'research',d:'Deep research multi-sources',p:'Synthèses R&D, rapports'},
|
|
{n:'Critic',e:'⚖️',z:1,t:'cognitive',d:'Validation & risques',p:'Reviews, alertes risques'},
|
|
{n:'Executor',e:'⚡',z:2,t:'cognitive',d:'Exécution & déploiement',p:'Scripts, migrations'},
|
|
{n:'Debugger',e:'🐛',z:2,t:'cognitive',d:'Root cause analysis',p:'Fixes, traces, patches'},
|
|
{n:'Reviewer',e:'👁️',z:2,t:'cognitive',d:'Code review expert',p:'PR reviews, scores qualité'},
|
|
{n:'Designer',e:'🎨',z:2,t:'cognitive',d:'UI/UX design system',p:'Mockups, composants'},
|
|
{n:'WEDROID',e:'🤖',z:2,t:'backend',d:'Auto-diagnostic backend v5',p:'DB fix, API repair auto'},
|
|
{n:'Simplifier',e:'✂️',z:2,t:'cognitive',d:'Refactoring & clean code',p:'Code -40% complexité'},
|
|
{n:'Watchdog',e:'🐕',z:3,t:'monitor',d:'Service monitor */3min',p:'Auto-restart + Telegram'},
|
|
{n:'Guardian',e:'🛡️',z:3,t:'monitor',d:'Protection fichiers système',p:'chattr +i, lockdown'},
|
|
{n:'Blade',e:'💻',z:3,t:'desktop',d:'Agent Razer Blade desktop',p:'PowerShell, sync, tasks'},
|
|
{n:'Git-Master',e:'🌿',z:3,t:'cognitive',d:'Git flow & releases',p:'Tags, merges, deploys'},
|
|
{n:'Security',e:'🔐',z:4,t:'cognitive',d:'Audit OWASP & pentests',p:'Rapports vulnérabilités'},
|
|
{n:'Verifier',e:'✅',z:4,t:'cognitive',d:'Conformité ISO/RGPD',p:'Checks PCI-DSS, audits'},
|
|
{n:'QA-Test',e:'🧪',z:5,t:'cognitive',d:'Tests E2E & couverture',p:'148 NonReg, Playwright'},
|
|
{n:'TestEng',e:'🧰',z:5,t:'cognitive',d:'CI/CD pipelines',p:'Automatisation tests'},
|
|
{n:'Tracer',e:'🔦',z:5,t:'cognitive',d:'Log tracing & debug',p:'Stack traces, analysis'},
|
|
{n:'Scientist',e:'🔬',z:5,t:'cognitive',d:'Benchmarks & métriques',p:'AI Benchmark 182 modèles'},
|
|
{n:'Explore',e:'🧭',z:6,t:'cognitive',d:'Exploration R&D pharma',p:'Nouvelles sources HCP'},
|
|
{n:'DocSpec',e:'📝',z:6,t:'cognitive',d:'Documentation technique',p:'Templates, guides'},
|
|
{n:'MiroFish',e:'🐟',z:6,t:'research',d:'Creative AI multi-agent',p:'Contenu, brainstorm'},
|
|
{n:'TaskMgr',e:'📋',z:7,t:'cognitive',d:'Suivi tâches & deadlines',p:'Kanban, alertes retard'},
|
|
{n:'Brain',e:'💡',z:7,t:'cognitive',d:'Brainstorming créatif',p:'Idées, innovation'},
|
|
{n:'Intro',e:'🧠',z:7,t:'cognitive',d:'Méta-analyse & réflexion',p:'Auto-amélioration IA'},
|
|
{n:'Orch',e:'🎯',z:7,t:'cognitive',d:'Orchestration multi-agent',p:'Coordination workflows'},
|
|
];
|
|
|
|
const TC={cognitive:'#3b82f6',autonomous:'#a855f7',backend:'#22c55e',monitor:'#f59e0b',pharma:'#ec4899',research:'#06b6d4',desktop:'#64748b'};
|
|
|
|
// ═══ INIT AGENTS ═══
|
|
const groundY=H*.56;
|
|
AG.forEach((a,i)=>{
|
|
a.x=0;a.y=0;a.tx=0;a.ty=0;a.bob=Math.random()*6.28;a.walk=Math.random()*6.28;
|
|
a.timer=Math.random()*200;a.speed=.8+Math.random()*.4;a.scale=1;a.glow=0;
|
|
a.breathe=Math.random()*6.28;a.eyeBlink=0;a.blinkTimer=100+Math.random()*300;
|
|
});
|
|
|
|
// ═══ PARTICLES ═══
|
|
const PTS=[];for(let i=0;i<120;i++)PTS.push({x:Math.random()*4000-500,y:Math.random()*H,r:Math.random()*1.8+.3,a:Math.random()*.2+.03,s:Math.random()*.4+.08,ph:Math.random()*6.28});
|
|
// ═══ ZONE LIGHTS ═══
|
|
const ZLIGHTS=[];ZN.forEach(z=>{for(let i=0;i<3;i++)ZLIGHTS.push({zx:0,ox:(Math.random()-.5)*80,oy:Math.random()*-40-20,r:40+Math.random()*60,a:.04+Math.random()*.04,clr:z.clr,z:z});});
|
|
|
|
function layZones(){
|
|
const gap=(W*1.1)/ZN.length;
|
|
ZN.forEach((z,i)=>{z.x=gap*.55+i*gap;});
|
|
// Init agent positions
|
|
AG.forEach(a=>{const z=ZN[a.z];if(z){const ais=AG.filter(b=>b.z===a.z);const mi=ais.indexOf(a);const spread=Math.min(gap*.35,100);a.x=z.x+(mi-ais.length/2)*26;a.y=groundY-10+Math.random()*15;a.tx=a.x;a.ty=a.y;}});
|
|
}
|
|
layZones();
|
|
|
|
// ═══ DRAW BACKGROUND ═══
|
|
function drawBg(){
|
|
// Gradient sky
|
|
const g=X.createLinearGradient(0,0,0,H);
|
|
g.addColorStop(0,'#020408');g.addColorStop(.3,'#040810');g.addColorStop(.55,'#060c18');g.addColorStop(1,'#030608');
|
|
X.fillStyle=g;X.fillRect(0,0,W,H);
|
|
|
|
// Grid floor
|
|
X.save();
|
|
X.globalAlpha=.08;
|
|
const gy=groundY+24;
|
|
for(let i=-20;i<40;i++){
|
|
const x=i*60-((frame*.3)%60);
|
|
X.strokeStyle='#06b6d4';X.lineWidth=.5;
|
|
X.beginPath();X.moveTo(x,gy);X.lineTo(x+(W*.3),H);X.stroke();
|
|
}
|
|
for(let j=0;j<12;j++){
|
|
const y=gy+j*((H-gy)/12);
|
|
X.beginPath();X.moveTo(0,y);X.lineTo(W,y);X.stroke();
|
|
}
|
|
X.restore();
|
|
}
|
|
|
|
// ═══ DRAW ZONE ═══
|
|
function drawZone(z,idx){
|
|
const x=z.x, y=groundY;
|
|
// Pillar glow
|
|
const g=X.createRadialGradient(x,y-30,5,x,y-30,120);
|
|
g.addColorStop(0,z.clr+'18');g.addColorStop(1,'transparent');
|
|
X.fillStyle=g;X.beginPath();X.arc(x,y-30,120,0,6.28);X.fill();
|
|
|
|
// Platform
|
|
X.fillStyle=z.clr+'15';
|
|
X.beginPath();
|
|
X.ellipse(x,y+22,70,10,0,0,6.28);
|
|
X.fill();
|
|
X.strokeStyle=z.clr+'40';X.lineWidth=1;
|
|
X.beginPath();X.ellipse(x,y+22,70,10,0,0,6.28);X.stroke();
|
|
|
|
// Label
|
|
X.font='900 10px Orbitron';X.textAlign='center';
|
|
X.fillStyle=z.clr+'90';
|
|
X.fillText(z.lbl,x,y+48);
|
|
|
|
// Icon
|
|
X.font='20px sans-serif';
|
|
X.fillText(z.icon,x,y-60);
|
|
|
|
// Connector to next
|
|
if(idx<ZN.length-1){
|
|
const nx=ZN[idx+1].x;
|
|
X.strokeStyle='#0a1428';X.lineWidth=2;X.setLineDash([6,10]);
|
|
X.beginPath();X.moveTo(x+72,y+22);X.lineTo(nx-72,y+22);X.stroke();
|
|
X.setLineDash([]);
|
|
// Animated dot
|
|
const t=(frame*1.5+idx*40)%((nx-x));
|
|
X.fillStyle=z.clr+'60';
|
|
X.beginPath();X.arc(x+72+t,y+22,2.5,0,6.28);X.fill();
|
|
}
|
|
}
|
|
|
|
// ═══ DRAW AGENT CHARACTER ═══
|
|
function drawAgent(a){
|
|
const c=TC[a.t]||'#6080a0';
|
|
const s=16*(a.scale);
|
|
const bob=Math.sin(a.bob)*2.5;
|
|
const leg=Math.sin(a.walk)*5;
|
|
const breath=Math.sin(a.breathe)*.5;
|
|
const isHov=a===hov;
|
|
|
|
X.save();
|
|
X.translate(a.x,a.y+bob);
|
|
|
|
// Shadow
|
|
X.fillStyle='rgba(0,0,0,.25)';
|
|
X.beginPath();X.ellipse(0,s*.6,s*.5,s*.15,0,0,6.28);X.fill();
|
|
|
|
if(isHov){
|
|
// Selection ring
|
|
X.strokeStyle=c;X.lineWidth=1.5;X.globalAlpha=.3+Math.sin(frame*.08)*.2;
|
|
X.beginPath();X.ellipse(0,s*.6,s*.8,s*.2,0,0,6.28);X.stroke();
|
|
X.globalAlpha=1;
|
|
// Glow
|
|
X.shadowColor=c;X.shadowBlur=24;
|
|
}
|
|
|
|
// ═ BODY (capsule shape) ═
|
|
X.fillStyle=c+'30';X.strokeStyle=c;X.lineWidth=1.8;
|
|
// Torso
|
|
X.beginPath();
|
|
X.moveTo(-s*.3,-s*1.5+breath);
|
|
X.quadraticCurveTo(-s*.35,-s*.6, -s*.25,-s*.2);
|
|
X.lineTo(s*.25,-s*.2);
|
|
X.quadraticCurveTo(s*.35,-s*.6, s*.3,-s*1.5+breath);
|
|
X.closePath();
|
|
X.fill();X.stroke();
|
|
|
|
// Head
|
|
const hr=s*.45;
|
|
X.beginPath();X.arc(0,-s*1.9,hr,0,6.28);
|
|
X.fillStyle=c+'20';X.fill();
|
|
X.strokeStyle=c;X.stroke();
|
|
|
|
// Visor / face glow
|
|
X.beginPath();X.arc(0,-s*1.9,hr*.6,-.4,.4);
|
|
X.strokeStyle=c+'80';X.lineWidth=2;X.stroke();
|
|
|
|
// Emoji
|
|
X.font=`${Math.round(s*.55)}px sans-serif`;X.textAlign='center';X.textBaseline='middle';
|
|
X.fillText(a.e,0,-s*1.9);
|
|
|
|
// Eyes blink
|
|
if(a.eyeBlink>0){
|
|
X.fillStyle='#020408';
|
|
X.fillRect(-s*.2,-s*2,s*.4,s*.12);
|
|
}
|
|
|
|
// Arms
|
|
X.strokeStyle=c;X.lineWidth=1.8;X.lineCap='round';
|
|
X.beginPath();
|
|
X.moveTo(-s*.5,-s*1.1+Math.sin(a.walk+1)*3);
|
|
X.lineTo(-s*.3,-s*1.3);
|
|
X.lineTo(s*.3,-s*1.3);
|
|
X.lineTo(s*.5,-s*1.1-Math.sin(a.walk+1)*3);
|
|
X.stroke();
|
|
|
|
// Legs
|
|
X.beginPath();
|
|
X.moveTo(-s*.3+leg*.4, s*.4);
|
|
X.lineTo(-s*.1, -s*.2);
|
|
X.lineTo(s*.1, -s*.2);
|
|
X.lineTo(s*.3-leg*.4, s*.4);
|
|
X.stroke();
|
|
|
|
// Boots
|
|
X.fillStyle=c+'50';
|
|
X.beginPath();X.ellipse(-s*.3+leg*.4,s*.45,s*.12,s*.06,0,0,6.28);X.fill();
|
|
X.beginPath();X.ellipse(s*.3-leg*.4,s*.45,s*.12,s*.06,0,0,6.28);X.fill();
|
|
|
|
// Name tag
|
|
if(isHov||true){
|
|
X.font=`${isHov?'700':'600'} ${isHov?10:8}px Rajdhani`;
|
|
X.textAlign='center';
|
|
X.fillStyle=isHov?'#fff':c+'70';
|
|
X.fillText(a.n,0,s*.8);
|
|
}
|
|
|
|
// Activity indicator (small orbiting dot)
|
|
const oA=frame*.04+a.bob;
|
|
const ox=Math.cos(oA)*s*.7, oy=-s*1.9+Math.sin(oA)*s*.35;
|
|
X.fillStyle=c;X.globalAlpha=.5;
|
|
X.beginPath();X.arc(ox,oy,1.5,0,6.28);X.fill();
|
|
X.globalAlpha=1;
|
|
|
|
X.restore();
|
|
}
|
|
|
|
// ═══ UPDATE ═══
|
|
function update(dt){
|
|
frame++;
|
|
AG.forEach(a=>{
|
|
a.bob+=dt*2.8*a.speed;
|
|
a.walk+=dt*(a.speed*5);
|
|
a.breathe+=dt*1.5;
|
|
a.timer-=dt*60;
|
|
a.blinkTimer-=dt*60;
|
|
|
|
if(a.blinkTimer<=0){a.eyeBlink=8;a.blinkTimer=120+Math.random()*400;}
|
|
if(a.eyeBlink>0)a.eyeBlink-=dt*60;
|
|
|
|
if(a.timer<=0){
|
|
a.timer=120+Math.random()*350;
|
|
const z=ZN[a.z];
|
|
const ais=AG.filter(b=>b.z===a.z);
|
|
const mi=ais.indexOf(a);
|
|
a.tx=z.x+(mi-ais.length/2)*24+(Math.random()-.5)*30;
|
|
a.ty=groundY-12+(Math.random()-.5)*18;
|
|
// Rare visit to neighbor
|
|
if(Math.random()<.04){
|
|
const nz=Math.max(0,Math.min(ZN.length-1,a.z+(Math.random()<.5?-1:1)));
|
|
a.tx=ZN[nz].x+(Math.random()-.5)*50;
|
|
a.ty=groundY-12+(Math.random()-.5)*14;
|
|
}
|
|
}
|
|
a.x+=(a.tx-a.x)*.018*a.speed;
|
|
a.y+=(a.ty-a.y)*.018*a.speed;
|
|
// Hover scale
|
|
a.scale+=(a===hov?1.35:1-a.scale)*.1;
|
|
a.glow+=(a===hov?1:0-a.glow)*.1;
|
|
});
|
|
}
|
|
|
|
// ═══ TOOLTIP ═══
|
|
function showTip(){
|
|
const t=document.getElementById('tip');
|
|
if(!hov){t.style.display='none';return;}
|
|
t.style.display='block';
|
|
t.style.left=Math.min(mx+20,W-280)+'px';
|
|
t.style.top=Math.max(my-160,10)+'px';
|
|
const c=TC[hov.t]||'#6080a0';
|
|
t.querySelector('.nm').textContent=hov.e+' '+hov.n;
|
|
t.querySelector('.tp').textContent=hov.t;
|
|
t.querySelector('.tp').style.background=c+'25';
|
|
t.querySelector('.tp').style.color=c;
|
|
t.querySelector('.ds').textContent=hov.d;
|
|
t.querySelector('.pr').textContent='→ '+hov.p;
|
|
t.querySelector('.bar i').style.background=`linear-gradient(90deg,${c},${c}80)`;
|
|
t.querySelector('.bar i').style.width='100%';
|
|
}
|
|
|
|
// ═══ HIT TEST ═══
|
|
function hitTest(){
|
|
hov=null;
|
|
AG.forEach(a=>{
|
|
if(Math.abs(mx-a.x)<20&&Math.abs(my-a.y+10)<30)hov=a;
|
|
});
|
|
}
|
|
|
|
// ═══ PARTICLES ═══
|
|
function drawPts(){
|
|
PTS.forEach(p=>{
|
|
p.y-=p.s;p.ph+=.01;
|
|
p.x+=Math.sin(p.ph)*.2;
|
|
if(p.y<-5){p.y=H+5;p.x=Math.random()*W*1.2-100;}
|
|
X.fillStyle=`rgba(6,182,212,${p.a})`;
|
|
X.beginPath();X.arc(p.x,p.y,p.r,0,6.28);X.fill();
|
|
});
|
|
}
|
|
|
|
// ═══ ZONE BAR ═══
|
|
function initZbar(){
|
|
const el=document.getElementById('zbar');
|
|
el.innerHTML=ZN.map(z=>`<div class="zb" onmouseenter="litZone('${z.id}')" onmouseleave="unlitZone()">${z.icon} ${z.lbl}</div>`).join('');
|
|
}
|
|
let litZ=null;
|
|
window.litZone=id=>{litZ=id;document.querySelectorAll('.zb').forEach((b,i)=>b.classList.toggle('lit',ZN[i].id===id));};
|
|
window.unlitZone=()=>{litZ=null;document.querySelectorAll('.zb').forEach(b=>b.classList.remove('lit'));};
|
|
initZbar();
|
|
|
|
// ═══ MAIN LOOP ═══
|
|
let lt=0,fpsC=0,fpsT=0;
|
|
function loop(t){
|
|
const dt=Math.min((t-lt)/1000,.04);lt=t;
|
|
fpsC++;if(t-fpsT>1000){document.getElementById('fps').textContent=fpsC;fpsC=0;fpsT=t;}
|
|
|
|
X.clearRect(0,0,W,H);
|
|
drawBg();
|
|
drawPts();
|
|
|
|
// Zone lights
|
|
ZLIGHTS.forEach(l=>{
|
|
const g=X.createRadialGradient(l.z.x+l.ox,groundY+l.oy,0,l.z.x+l.ox,groundY+l.oy,l.r);
|
|
g.addColorStop(0,l.clr+Math.round(l.a*255).toString(16).padStart(2,'0'));
|
|
g.addColorStop(1,'transparent');
|
|
X.fillStyle=g;X.beginPath();X.arc(l.z.x+l.ox,groundY+l.oy,l.r,0,6.28);X.fill();
|
|
});
|
|
|
|
ZN.forEach((z,i)=>drawZone(z,i));
|
|
update(dt);
|
|
|
|
// Draw agents (sorted by Y for depth)
|
|
const sorted=[...AG].sort((a,b)=>a.y-b.y);
|
|
sorted.forEach(a=>{
|
|
// Dim if zone filter active
|
|
if(litZ){const zIdx=ZN.findIndex(z=>z.id===litZ);X.globalAlpha=a.z===zIdx?1:.15;}
|
|
drawAgent(a);
|
|
X.globalAlpha=1;
|
|
});
|
|
|
|
hitTest();
|
|
showTip();
|
|
requestAnimationFrame(loop);
|
|
}
|
|
|
|
C.addEventListener('mousemove',e=>{mx=e.clientX;my=e.clientY});
|
|
C.addEventListener('mouseleave',()=>{mx=my=-1});
|
|
addEventListener('resize',()=>{resize();layZones()});
|
|
|
|
requestAnimationFrame(loop);
|
|
</script>
|
|
<!-- CARTO_REMOVED -->
|
|
</body>
|
|
</html>
|