Files
html/ai-benchmark.html

292 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>WEVAL — AI Benchmark vs OPUS 4.6</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{--bg:#06080f;--bg2:#0d1117;--bg3:#161b22;--bd:#21262d;--bd2:#30363d;--wh:#e6edf3;--mu:#7d8590;--mu2:#8b949e;--ac:#f0883e;--gn:#3fb950;--bl:#58a6ff;--cy:#56d4dd;--rd:#f85149;--pk:#db61a2;--vi:#a371f7;--go:#d29922;--mono:'JetBrains Mono',monospace;--font:'Plus Jakarta Sans',sans-serif}
body{background:var(--bg);color:var(--wh);font-family:var(--font);min-height:100vh}
a{color:var(--cy);text-decoration:none}
.hdr{background:linear-gradient(135deg,#0d1117,#161040,#0d1117);border-bottom:1px solid var(--bd);padding:18px 28px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px}
.hdr h1{font-size:18px;font-weight:800}.hdr h1 span{background:linear-gradient(135deg,var(--vi),var(--pk));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.hdr-sub{font-size:10px;color:var(--mu);font-family:var(--mono);margin-top:2px}
.main{max-width:1500px;margin:0 auto;padding:20px}
.stats{display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-bottom:16px}
.stat{background:var(--bg2);border:1px solid var(--bd);border-radius:8px;padding:14px 16px;position:relative;overflow:hidden}
.stat::after{content:'';position:absolute;top:0;left:0;right:0;height:2px}
.stat:nth-child(1)::after{background:var(--vi)}.stat:nth-child(2)::after{background:var(--gn)}.stat:nth-child(3)::after{background:var(--bl)}.stat:nth-child(4)::after{background:var(--ac)}.stat:nth-child(5)::after{background:var(--pk)}.stat:nth-child(6)::after{background:var(--cy)}
.st-l{font-size:9px;color:var(--mu);text-transform:uppercase;letter-spacing:.8px;font-weight:600}.st-v{font-size:24px;font-weight:800;font-family:var(--mono);margin:3px 0}.st-s{font-size:9px;color:var(--mu2)}
.grid{display:grid;grid-template-columns:1fr;gap:12px;margin-bottom:12px}
.card{background:var(--bg2);border:1px solid var(--bd);border-radius:8px;overflow:hidden}
.card-h{padding:12px 16px;border-bottom:1px solid var(--bd);display:flex;justify-content:space-between;align-items:center}
.card-t{font-size:12px;font-weight:700}.card-b{padding:12px 16px}
.badge{font-size:9px;padding:2px 8px;border-radius:12px;font-weight:600;font-family:var(--mono)}
.b-vi{background:rgba(163,113,247,.12);color:var(--vi)}.b-gn{background:rgba(63,185,80,.12);color:var(--gn)}.b-bl{background:rgba(88,166,255,.12);color:var(--bl)}
.full{grid-column:1/-1}
.lb{display:flex;flex-direction:column;gap:3px}
.lb-row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:5px;transition:background .15s}
.lb-row:hover{background:var(--bg3)}
.lb-rank{font-size:11px;font-weight:800;font-family:var(--mono);width:24px;text-align:center;color:var(--mu)}
.lb-icon{font-size:15px;width:22px;text-align:center}
.lb-info{flex:1;min-width:0}
.lb-name{font-size:11px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lb-desc{font-size:8px;color:var(--mu);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px}
.lb-wired{font-size:8px;padding:1px 4px;border-radius:4px;font-weight:700}
.lb-usecase{font-size:7px;color:var(--fg2);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;opacity:.8}
.lb-cost{font-size:7px;white-space:nowrap}
.lb-usage{font-size:8px;color:var(--fg3);max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lb-type{font-size:7px;padding:1px 5px;border-radius:8px;font-weight:700;white-space:nowrap}
.lb-bar-w{flex:0 0 140px;height:5px;background:var(--bg);border-radius:3px;overflow:hidden}
.lb-bar{height:100%;border-radius:3px;transition:width 1.2s cubic-bezier(.16,1,.3,1)}
.lb-sc{font-family:var(--mono);font-weight:700;font-size:12px;width:36px;text-align:right}
.lb-pct{font-family:var(--mono);font-size:9px;width:36px;text-align:right;color:var(--mu)}
.mx{width:100%;border-collapse:collapse;font-size:10px}
.mx th{padding:2px 3px;font-size:6px;text-transform:uppercase;letter-spacing:.5px;color:var(--mu);border-bottom:1px solid var(--bd2);font-weight:600;text-align:center;position:sticky;top:0;background:var(--bg2)}
.mx th:first-child{text-align:left}
.mx td{padding:2px 3px;border-bottom:1px solid var(--bd);text-align:center;font-family:var(--mono);font-weight:600;font-size:8px}
.mx td:first-child{text-align:left;font-family:var(--font);font-weight:600}
.mx tr:hover{background:var(--bg3)}
.sc-h{color:var(--gn)}.sc-m{color:var(--go)}.sc-l{color:var(--rd)}
.caps{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:3px}
.cap{background:var(--bg);border:1px solid var(--bd);border-radius:3px;padding:3px 5px;font-size:8px;display:flex;justify-content:space-between;align-items:center}
.cap-n{color:var(--mu2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:70px}
.cap-v{font-family:var(--mono);font-weight:700;font-size:9px}
.gap{padding:6px 10px;margin:3px 0;background:var(--bg);border:1px solid var(--bd);border-radius:5px;font-size:9px}
.gap .ai{color:var(--vi);font-weight:700}.gap .fix{color:var(--gn);font-size:8px;margin-top:2px}
@media(max-width:1200px){.stats{grid-template-columns:repeat(3,1fr)}.grid{grid-template-columns:1fr}}
@media(max-width:768px){.stats{grid-template-columns:repeat(2,1fr)}.lb-bar-w{flex:0 0 80px}.lb-desc{display:none}}
/* === WEVIA Gemini Rolling v2 VISIBLE Enrichment (wave 306 batch) === */
.kpi,[class*="card"],[class*="panel"],[class*="room"],.stat-card,.metric-card,.hub-card,.widget,.stat,.box{position:relative!important}
.kpi,[class*="card"],.stat-card,.metric-card,.hub-card{animation:geV2Entrance .8s cubic-bezier(.34,1.56,.64,1) backwards}
.kpi:nth-child(1),[class*="card"]:nth-child(1){animation-delay:0s}
.kpi:nth-child(2),[class*="card"]:nth-child(2){animation-delay:.09s}
.kpi:nth-child(3),[class*="card"]:nth-child(3){animation-delay:.18s}
.kpi:nth-child(4),[class*="card"]:nth-child(4){animation-delay:.27s}
.kpi:nth-child(5),[class*="card"]:nth-child(5){animation-delay:.36s}
.kpi:nth-child(6),[class*="card"]:nth-child(6){animation-delay:.45s}
@keyframes geV2Entrance{from{opacity:0;transform:translateY(24px) scale(.94)}to{opacity:1;transform:translateY(0) scale(1)}}
.kpi,[class*="card"],.stat-card,.metric-card,.hub-card,.widget{border:1px solid transparent!important;box-shadow:0 0 0 1px rgba(236,72,153,.15),0 4px 16px rgba(0,0,0,.25)!important;transition:box-shadow .4s,transform .3s cubic-bezier(.34,1.56,.64,1),filter .3s!important}
.kpi:hover,[class*="card"]:hover,.stat-card:hover,.metric-card:hover,.hub-card:hover{transform:translateY(-6px) scale(1.03)!important;filter:brightness(1.2)!important;box-shadow:0 0 0 2px rgba(236,72,153,.6),0 12px 32px rgba(236,72,153,.25),0 0 24px rgba(78,205,196,.2)!important}
.kpi::before,[class*="card"]::before,.stat-card::before,.metric-card::before,.hub-card::before{content:"";position:absolute;top:12px;right:12px;width:10px;height:10px;border-radius:50%;background:radial-gradient(circle,#2ed573,#1a9a4e);box-shadow:0 0 12px #2ed573,0 0 24px rgba(46,213,115,.5);animation:geV2Pulse 1.6s ease-out infinite;z-index:100;pointer-events:none}
@keyframes geV2Pulse{0%{transform:scale(1);box-shadow:0 0 12px #2ed573,0 0 24px rgba(46,213,115,.5)}50%{transform:scale(1.4);box-shadow:0 0 20px #2ed573,0 0 40px rgba(46,213,115,.8)}100%{transform:scale(1);box-shadow:0 0 12px #2ed573,0 0 24px rgba(46,213,115,.5)}}
body::after{content:"";position:fixed;inset:0;pointer-events:none;background:radial-gradient(ellipse at 70% 30%,transparent 40%,rgba(236,72,153,.06) 100%),radial-gradient(ellipse at 30% 70%,transparent 40%,rgba(78,205,196,.04) 100%);animation:geV2Ambient 10s ease-in-out infinite;z-index:0}
@keyframes geV2Ambient{0%,100%{opacity:.5}50%{opacity:1}}
h1,.header-title,.main-title,.hub-title,.page-title{background-image:linear-gradient(90deg,currentColor 0%,currentColor 40%,rgba(236,72,153,1) 50%,currentColor 60%,currentColor 100%)!important;background-size:200% auto!important;-webkit-background-clip:text!important;background-clip:text!important;-webkit-text-fill-color:transparent!important;animation:geV2Shimmer 5s linear infinite!important}
@keyframes geV2Shimmer{0%{background-position:200% center}100%{background-position:-200% center}}
/* Doctrine zero chevauchement - hide common offenders */
.opus-x-btn,.toggle-top-right-btn,.fab-corner{display:none!important}
/* === end WEVIA Gemini Rolling v2 batch === */
</style>
<link rel="stylesheet" href="/css/weval-premium.css">
<script src="/js/wevia-a11y-auto.js" defer></script>
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-143213 -->
<style id="doctrine60-ux-direct">
/* DOCTRINE-60-UX-ENRICH injected-direct */
body::before {
content: '';
position: fixed;
top: 0; left: 0; width: 100vw; height: 100vh;
background: radial-gradient(circle at 50% 50%, rgba(100,180,255,0.08), transparent 60%);
pointer-events: none;
z-index: -1;
}
.card, .kpi, .panel, .btn {
transition: all 0.3s cubic-bezier(0.2,0,0.1,1);
}
.card:hover, .kpi:hover, .panel:hover {
box-shadow: 0 4px 20px rgba(100,180,255,0.2);
border-color: rgba(100,180,255,0.5);
}
@keyframes pulseD60 {
0%,100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(1.05); }
}
.pulse, .live-indicator, .active, .online {
animation: pulseD60 3s ease-in-out infinite;
}
.modal, .chat, .speech, .overlay {
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.enter-stagger {
animation: enterStagD60 0.5s cubic-bezier(0.2,0,0.1,1) forwards;
}
@keyframes enterStagD60 {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<!-- BETON-DOCTRINE-101 dual-dummy block (pages pub) -->
<div id="weval-global-logout" style="display:none!important;visibility:hidden!important" aria-hidden="true" data-beton-101="dummy-to-block-auto-injection"></div>
<a id="weval-gl" href="#" style="display:none!important;visibility:hidden!important" aria-hidden="true" data-beton-101="dummy-to-block-auto-injection" tabindex="-1"></a>
<div class="hdr">
<div style="display:flex;align-items:center;gap:14px">
<div style="width:36px;height:36px;background:linear-gradient(135deg,var(--vi),var(--pk));border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:17px">🏆</div>
<div><h1><span>AI Benchmark</span> vs OPUS 4.6</h1><div class="hdr-sub">129 AIs &bull; 40 Categories &bull; 102% OPUS Sovereign</div></div>
</div>
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
<a href="/oss-discovery.html" style="font-size:9px;padding:5px 10px;border:1px solid var(--bd);border-radius:4px;color:var(--mu2)">OSS 153 tools</a>
<a href="/wevia" style="font-size:9px;padding:5px 10px;border:1px solid var(--bd);border-radius:4px;color:var(--mu2)">WEVIA</a>
<a href="/wevads-ia/" style="font-size:9px;padding:5px 10px;border:1px solid var(--bd);border-radius:4px;color:var(--mu2)">WEVADS IA</a>
<span class="badge b-gn">&bull; Live</span>
</div>
</div>
<div class="main" id="app"><div style="text-align:center;padding:60px;color:var(--mu)">Loading benchmark data...</div></div>
<script>
const CACHE='/api/ai-benchmark-cache.json';
const COL={reference:'#d29922',chatbot:'#58a6ff',backend:'#f0883e',agent:'#3fb950',sovereign:'#a371f7',security:'#f85149',search:'#56d4dd',testing:'#db61a2',memory:'#8b949e',knowledge:'#f0883e',automation:'#7d8590',composite:'#d29922',ecosystem:'#3fb950'};
const BG={reference:'rgba(210,153,34,.1)',chatbot:'rgba(88,166,255,.1)',backend:'rgba(240,136,62,.1)',agent:'rgba(63,185,80,.1)',sovereign:'rgba(163,113,247,.1)',security:'rgba(248,81,73,.1)',search:'rgba(86,212,221,.1)',testing:'rgba(219,97,162,.1)',memory:'rgba(139,148,158,.1)',knowledge:'rgba(240,136,62,.1)',automation:'rgba(125,133,144,.1)',composite:'rgba(210,153,34,.1)',ecosystem:'rgba(63,185,80,.1)'};
function sc(s){return s>=75?'sc-h':s>=55?'sc-m':'sc-l'}
async function load(){try{const r=await fetch(CACHE+'?t='+Date.now());render(await r.json())}catch(e){document.getElementById('app').innerHTML='<p style="color:var(--rd)">'+e+'</p>'}}
function render(c){
const A=c.all_ais||{},comp=c.composite||{},lb=c.leaderboard||{},gen=c.generated||'',R=c.report||{};
const S=(Array.isArray(lb)?lb:Object.entries(lb).map(e=>({name:e[0],score:e[1]}))).sort((a,b)=>(b.score||0)-(a.score||0)),mx=S[0]?S[0].score:90;
const UM=c.usage_map||{};
const cats=Object.keys(comp).sort((a,b)=>(comp[b]||0)-(comp[a]||0));
const cbs=['WEVIA','WEVCODE','MANAGER','DeerFlow','SENTINEL','Ethica_Chat','WEVADS_IA','Qwen3_235b_Cerebras','Mistral_Small_EU','DeepSeekV31_SambaNova','ChatGPT_4o','Gemini_Pro','Claude_Opus'];
const infras=Object.entries(A).filter(([n,a])=>!['OPUS','COMPOSITE','ECOSYSTEM',...cbs].includes(n));
let h=`<div class="stats">
<div class="stat"><div class="st-l">AIs</div><div class="st-v">${S.length}</div><div class="st-s">Cloud+Sovereign+Agents</div></div>
<div class="stat"><div class="st-l">Categories</div><div class="st-v">${cats.length}</div><div class="st-s">Strategy to AI Ethics</div></div>
<div class="stat"><div class="st-l">Combined</div><div class="st-v" style="color:var(--bl)">${R.combined_avg||R.composite_avg||'?'}/90</div><div class="st-s">${Math.round((R.combined_avg||R.composite_avg||0)/90*100)}% OPUS</div></div>
<div class="stat"><div class="st-l">Infra</div><div class="st-v" style="color:var(--gn)">${R.infra_avg||'?'}/90</div><div class="st-s">${Math.round((R.infra_avg||0)/90*100)}% OPUS</div></div>
<div class="stat"><div class="st-l">Ecosystem</div><div class="st-v" style="color:var(--vi)">${R.ecosystem||'?'}/90</div><div class="st-s">${Math.round((R.ecosystem||0)/90*100)}% OPUS</div></div>
<div class="stat"><div class="st-l">Updated</div><div class="st-v" style="font-size:10px">${gen.replace('T',' ').slice(0,16)}</div><div class="st-s">Daily 5h cron</div></div>
</div>
<div class="grid">
<div class="card"><div class="card-h"><div class="card-t">🏆 Leaderboard</div><span class="badge b-vi">${S.length} AIs</span></div>
<div class="card-b"><div class="lb">
${S.map((item,i)=>{const n=item.name||item[0]||"?";const avg=item.score||item[1]||0;const a=A[n]||{};const t=a.type||'?';const col=COL[t]||'#7d8590';const bg=BG[t]||'';
const pct=Math.round(avg/mx*100);const vO=Math.round(avg/90*100);
return `<div class="lb-row">
<div class="lb-rank" style="${i<3?'color:var(--go)':''}">${i+1}</div>
<div class="lb-icon">${a.icon||'?'}</div>
<div class="lb-info"><div class="lb-name"><a href="${a.url||'#'}" target="_blank" style="color:inherit;text-decoration:none">${n}</a></div><div class="lb-desc">${a.tier?'<span style="font-size:7px;opacity:.7">'+a.tier+'</span> ':''} ${(a.desc||'').slice(0,35)}</div></div>
<div class="lb-type" style="background:${bg};color:${col}">${t}</div>
<div class="lb-usage">${a.used_in||""}</div>
<div class="lb-wired">${a.wired?"<span style=\"background:#238636;color:#fff;padding:1px 4px;border-radius:4px;font-size:7px\">✅ WIRED</span>":"<span style=\"background:#da3633;color:#fff;padding:1px 4px;border-radius:4px;font-size:7px\">❌</span>"}</div>
<div class="lb-cost">${a.cost||""}</div>
<div class="lb-usecase">${a.usecase||""}</div>
<div class="lb-bar-w"><div class="lb-bar" style="width:${pct}%;background:${col}" data-w="${pct}%"></div></div>
<div class="lb-sc" style="color:${col}">${avg}</div>
<div class="lb-pct">${vO}%</div>
</div>`}).join('')}
</div></div></div>
<div class="card"><div class="card-h"><div class="card-t">📊 AI Capability Matrix</div><span class="badge b-bl">${cats.length}x${cbs.length} (${Object.keys(A).length} total)</span></div>
<div class="card-b" style="padding:0;overflow:auto;max-height:720px">
<table class="mx"><tr><th>Category</th>${cbs.map(a=>`<th>${a}</th>`).join('')}<th style="color:var(--go)">BEST</th><th>OPUS</th></tr>
${cats.map(cat=>{const b=comp[cat]||0;return `<tr><td>${cat}</td>${cbs.map(ai=>{const s=A[ai]?.caps?.[cat]||0;return `<td class="${sc(s)}">${s||'-'}</td>`}).join('')}<td class="${sc(b)}" style="font-weight:800">${b}</td><td style="color:var(--go)">90</td></tr>`}).join('')}
<tr style="border-top:2px solid var(--bd2)"><td style="font-weight:800">AVG</td>${cbs.map(ai=>`<td class="${sc(A[ai]?.avg||0)}" style="font-weight:800">${A[ai]?.avg||'?'}</td>`).join('')}<td style="font-weight:800;color:var(--go)">${R.composite_avg}</td><td style="color:var(--go);font-weight:800">90</td></tr>
</table></div></div></div>
<div class="grid">${infras.sort((a,b)=>(b[1].avg||0)-(a[1].avg||0)).map(([n,ai])=>`<div class="card">
<div class="card-h"><div class="card-t">${ai.icon||'?'} ${n}</div><div style="display:flex;gap:5px;align-items:center">
<span class="lb-type" style="background:${BG[ai.type]||''};color:${COL[ai.type]||'#7d8590'}">${ai.type}</span>
<span class="badge b-vi">${ai.avg}/90</span></div></div>
<div class="card-b"><div style="font-size:9px;color:var(--mu2);margin-bottom:6px">${ai.desc||''}</div>
<div class="caps">${Object.entries(ai.caps||{}).sort((a,b)=>b[1]-a[1]).map(([k,v])=>`<div class="cap"><span class="cap-n">${k.replace(/_/g,' ')}</span><span class="cap-v ${sc(v)}">${v}</span></div>`).join('')}</div>
</div></div>`).join('')}</div>
<div class="card full" style="margin-top:8px"><div class="card-h"><div class="card-t">💡 Gap to OPUS 4.6</div></div><div class="card-b">
${cats.filter(c=>(comp[c]||0)<70).map(cat=>{const s=comp[cat]||0;return `<div class="gap"><span class="ai">${cat}</span> ${s}/90 (${Math.round(s/90*100)}%) — need +${90-s}<div class="fix">Enrich domain prompt + structured templates</div></div>`}).join('')||'<p style="color:var(--gn);text-align:center;padding:12px">All categories above 70</p>'}
</div></div>`;
document.getElementById('app').innerHTML=h;
setTimeout(()=>{document.querySelectorAll('.lb-bar').forEach(b=>{const w=b.dataset.w;if(w){b.style.width='0';requestAnimationFrame(()=>setTimeout(()=>b.style.width=w,50))}})},100);
}
load();
</script>
<div style="margin:10px 0;padding:8px;background:#1a1a2e;border-radius:8px;font-size:12px">📊 Ref: <a href="https://lmarena.ai" target="_blank" style="color:#6d28d9">LMArena Chatbot Arena</a> | <a href="https://huggingface.co/spaces/open-llm-leaderboard" target="_blank" style="color:#6d28d9">HF Open LLM Leaderboard</a></div>
<!-- === OPUS UNIVERSAL DRILL-DOWN v1 19avr — append-only, doctrine #14 === -->
<script>
(function(){
if (window.__opusUniversalDrill) return; window.__opusUniversalDrill = true;
var d = document;
var m = d.createElement('div');
m.id = 'opus-udrill';
m.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.82);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:99995;padding:20px;cursor:pointer';
var inner = d.createElement('div');
inner.id = 'opus-udrill-in';
inner.style.cssText = 'max-width:900px;width:100%;max-height:90vh;overflow:auto;background:#0b0d15;border:1px solid rgba(99,102,241,0.35);border-radius:14px;padding:28px;cursor:default;box-shadow:0 20px 60px rgba(0,0,0,0.6);color:#e2e8f0;font:14px/1.55 Inter,system-ui,sans-serif';
inner.addEventListener('click', function(e){ e.stopPropagation(); });
m.appendChild(inner);
m.addEventListener('click', function(){ m.style.display='none'; });
d.addEventListener('keydown', function(e){ if(e.key==='Escape') m.style.display='none'; });
(d.body || d.documentElement).appendChild(m);
function openCard(card) {
// Clone card content + show close btn + increase font-size
var html = '<div style="display:flex;justify-content:flex-end;margin-bottom:14px"><button id="opus-udrill-close" style="padding:6px 14px;background:#171b2a;border:1px solid rgba(99,102,241,0.25);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:12px">✕ Fermer (Esc)</button></div>';
html += '<div style="transform-origin:top left;font-size:1.05em">' + card.outerHTML + '</div>';
inner.innerHTML = html;
d.getElementById('opus-udrill-close').onclick = function(){ m.style.display='none'; };
m.style.display = 'flex';
}
function wire(root) {
var sels = '.card,[class*="card"],.kpi,[class*="kpi"],.stat,[class*="stat"],.tile,[class*="tile"],.metric,[class*="metric"],.widget,[class*="widget"]';
var cards = root.querySelectorAll(sels);
for (var i = 0; i < cards.length; i++) {
var c = cards[i];
if (c.__opusWired) continue;
if (c.closest('button, a, input, select, textarea, #opus-udrill')) continue;
var r = c.getBoundingClientRect();
if (r.width < 60 || r.height < 40) continue;
c.__opusWired = true;
c.style.cursor = 'pointer';
c.setAttribute('role','button');
c.setAttribute('tabindex','0');
c.addEventListener('click', function(ev){
// If a more-specific drill is already active (e.g. pp-card custom), let it handle
if (ev.target.closest('[data-pp-id]') && window.__opusDrillInit) return;
if (ev.target.closest('a,button,input,select')) return;
ev.preventDefault(); ev.stopPropagation();
openCard(this);
});
c.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();openCard(this);} });
}
}
// Initial + mutation observer
var initRun = function(){ wire(d.body || d.documentElement); };
if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', initRun);
else initRun();
var mo = new MutationObserver(function(muts){
var newCard = false;
for (var i=0;i<muts.length;i++) if (muts[i].addedNodes.length) { newCard = true; break; }
if (newCard) initRun();
});
mo.observe(d.body || d.documentElement, {childList:true, subtree:true});
})();
</script>
<!-- === OPUS UNIVERSAL DRILL-DOWN END === -->
<script src="/api/a11y-auto-enhancer.js" defer></script>
<!-- WTP_UDOCK_V1 (Opus 21-avr t33b6) --><script src="/wtp-unified-dock.js" defer></script>
<script src="/opus-antioverlap-doctrine.js?v=1776776094" defer></script>
<!-- DOCTRINE-60-UX-JS --><script id="doctrine60-ux-js-direct">
// DOCTRINE-60-UX-JS staggered entrance
(function(){
if (!('IntersectionObserver' in window)) return;
const obs = new IntersectionObserver((entries) => {
entries.forEach((e, i) => {
if (e.isIntersecting) {
setTimeout(() => e.target.classList.add('enter-stagger'), i * 80);
obs.unobserve(e.target);
}
});
});
document.querySelectorAll('.card, .kpi, .panel').forEach(el => obs.observe(el));
})();
</script>
</body>
</html>