Files
html/weval-wiring.html

434 lines
25 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>WEVAL — Wiring & Comparison Dashboard</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Outfit:wght@300;500;700;900&display=swap" rel="stylesheet">
<style>
:root{--bg:#0a0e17;--bg2:#111827;--bg3:#1e293b;--bd:#1e293b;--fg:#e2e8f0;--fg2:#94a3b8;--fg3:#64748b;--go:#f59e0b;--gn:#22c55e;--rd:#ef4444;--bl:#3b82f6;--vi:#8b5cf6;--cy:#06b6d4;--pk:#ec4899}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font-family:'Outfit',sans-serif;min-height:100vh}
.top{background:linear-gradient(135deg,#0f172a 0%,#1a1040 50%,#0f2027 100%);padding:24px 32px;border-bottom:1px solid var(--bd)}
.top h1{font-size:28px;font-weight:900;background:linear-gradient(90deg,var(--go),var(--pk),var(--vi));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.top p{color:var(--fg2);font-size:13px;margin-top:4px}
.tabs{display:flex;gap:2px;padding:8px 32px;background:var(--bg2);border-bottom:1px solid var(--bd)}
.tab{padding:8px 20px;cursor:pointer;border-radius:6px 6px 0 0;font-size:12px;font-weight:700;color:var(--fg3);transition:.2s}
.tab:hover{color:var(--fg);background:var(--bg3)}
.tab.on{color:var(--go);background:var(--bg);border:1px solid var(--bd);border-bottom:1px solid var(--bg)}
.main{padding:24px 32px}
.panel{display:none}
.panel.on{display:block}
.kpis{display:grid;grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:24px}
.kpi{background:var(--bg2);border:1px solid var(--bd);border-radius:8px;padding:14px;text-align:center}
.kpi-v{font-size:28px;font-weight:900;font-family:'JetBrains Mono'}
.kpi-l{font-size:10px;color:var(--fg3);margin-top:2px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;margin-top:16px}
.card{background:var(--bg2);border:1px solid var(--bd);border-radius:8px;padding:14px;transition:.2s}
.card:hover{border-color:var(--vi);transform:translateY(-2px)}
.card-h{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
.card-n{font-weight:700;font-size:14px}
.card-n a{color:inherit;text-decoration:none}
.card-n a:hover{color:var(--go)}
.tag{font-size:8px;padding:2px 6px;border-radius:4px;font-weight:700;text-transform:uppercase}
.tag-gn{background:rgba(34,197,94,.2);color:var(--gn)}
.tag-rd{background:rgba(239,68,68,.2);color:var(--rd)}
.tag-bl{background:rgba(59,130,246,.2);color:var(--bl)}
.tag-vi{background:rgba(139,92,246,.2);color:var(--vi)}
.tag-go{background:rgba(245,158,11,.2);color:var(--go)}
.card-row{display:flex;justify-content:space-between;font-size:11px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.03)}
.card-row:last-child{border:0}
.card-k{color:var(--fg3)}
.card-v{font-family:'JetBrains Mono';font-size:10px}
.bar{height:4px;background:var(--bg3);border-radius:2px;margin-top:6px;overflow:hidden}
.bar-f{height:100%;border-radius:2px;transition:width 1s ease}
table{width:100%;border-collapse:collapse;font-size:11px}
th{text-align:left;padding:8px;color:var(--fg3);border-bottom:1px solid var(--bd);font-size:9px;text-transform:uppercase;position:sticky;top:0;background:var(--bg)}
td{padding:6px 8px;border-bottom:1px solid rgba(255,255,255,.03)}
tr:hover{background:var(--bg3)}
.dot{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:4px}
.dot-gn{background:var(--gn)}.dot-rd{background:var(--rd)}.dot-go{background:var(--go)}
.sc{font-family:'JetBrains Mono';font-weight:700}
.opus-bar{display:flex;align-items:center;gap:8px;margin:16px 0;padding:12px;background:var(--bg2);border:1px solid var(--bd);border-radius:8px}
.opus-bar .bar{flex:1;height:8px}
.loading{text-align:center;padding:40px;color:var(--fg3)}
@media(max-width:768px){.kpis{grid-template-columns:repeat(3,1fr)}.grid{grid-template-columns:1fr}}
</style>
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-143919 -->
<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>
<div class="top">
<h1>⚡ WEVAL Wiring & Comparison</h1>
<p>Dashboard dynamique — Benchmark IA × OSS Discovery × Architecture · Données temps réel</p>
</div>
<div class="tabs">
<div class="tab on" onclick="sw(0)">🔌 Wiring Map</div>
<div class="tab" onclick="sw(1)">⚔️ WEVAL vs Opus</div>
<div class="tab" onclick="sw(2)">🧩 OSS Skills</div>
<div class="tab" onclick="sw(3)">📊 Full Comparison</div>
</div>
<div class="main">
<!-- TAB 0: WIRING MAP -->
<div class="panel on" id="p0"><div class="loading">Chargement...</div></div>
<!-- TAB 1: WEVAL vs OPUS -->
<div class="panel" id="p1"><div class="loading">Chargement...</div></div>
<!-- TAB 2: OSS SKILLS -->
<div class="panel" id="p2"><div class="loading">Chargement...</div></div>
<!-- TAB 3: FULL COMPARISON TABLE -->
<div class="panel" id="p3"><div class="loading">Chargement...</div></div>
</div>
<script>
const $ = id => document.getElementById(id);
function sw(n){document.querySelectorAll('.tab').forEach((t,i)=>{t.classList.toggle('on',i===n)});document.querySelectorAll('.panel').forEach((p,i)=>{p.classList.toggle('on',i===n)})}
let BENCH={},OSS={},SOT={};
async function load(){
try{
const [b,o,s]=await Promise.all([
fetch('/api/ai-benchmark-cache.json?t='+Date.now()).then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})),
fetch('/api/oss-cache.json?t='+Date.now()).then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).catch(()=>({})),
fetch('/api/source-of-truth.json?t='+Date.now()).then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).catch(()=>({}))
]);
BENCH=b;OSS=o;SOT=s;
renderWiring();renderVsOpus();renderOSS();renderComparison();
}catch(e){$('p0').innerHTML='<p style="color:var(--rd)">Erreur: '+e+'</p>'}
}
function renderWiring(){
const A=BENCH.all_ais||{};
const wired=Object.entries(A).filter(([n,a])=>a.wired);
const notWired=Object.entries(A).filter(([n,a])=>!a.wired);
const agents=SOT.agents||{};
let h=`<div class="kpis">
<div class="kpi"><div class="kpi-v" style="color:var(--gn)">${wired.length}</div><div class="kpi-l">WIRED</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--rd)">${notWired.length}</div><div class="kpi-l">NOT WIRED</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--vi)">${Object.keys(A).length}</div><div class="kpi-l">TOTAL AIs</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--go)">${BENCH.report?.combined_avg||'?'}/90</div><div class="kpi-l">COMBINED</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--cy)">0€</div><div class="kpi-l">COÛT TOTAL</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--gn)">${Math.round(wired.length/Object.keys(A).length*100)}%</div><div class="kpi-l">COVERAGE</div></div>
</div>`;
h+=`<h3 style="color:var(--gn);margin-bottom:12px">✅ ${wired.length} IAs Wirées dans WEVAL</h3><div class="grid">`;
wired.sort((a,b)=>(b[1].avg||0)-(a[1].avg||0));
for(const [n,a] of wired){
const col=a.type==='sovereign'?'var(--vi)':a.type==='agent'?'var(--gn)':a.type==='security'?'var(--rd)':a.type==='chatbot'?'var(--bl)':a.type==='search'?'var(--cy)':a.type==='testing'?'var(--pk)':a.type==='composite'?'var(--go)':'var(--fg3)';
h+=`<div class="card">
<div class="card-h"><div class="card-n"><a href="${a.url||'#'}" target="_blank">${a.icon||''} ${n}</a></div><span class="tag tag-gn">✅ WIRED</span></div>
<div class="card-row"><span class="card-k">Type</span><span class="tag" style="background:${col}22;color:${col}">${a.type}</span></div>
<div class="card-row"><span class="card-k">Score</span><span class="card-v sc" style="color:${col}">${a.avg}/90</span></div>
<div class="card-row"><span class="card-k">Rôle</span><span class="card-v">${(a.used_in||'').slice(0,40)}</span></div>
<div class="card-row"><span class="card-k">Cas d'usage</span><span class="card-v">${(a.usecase||'').slice(0,40)}</span></div>
<div class="card-row"><span class="card-k">Coût</span><span class="card-v">${a.cost||'?'}</span></div>
<div class="bar"><div class="bar-f" style="width:${Math.round(a.avg/90*100)}%;background:${col}"></div></div>
</div>`;
}
h+=`</div>`;
if(notWired.length){
h+=`<h3 style="color:var(--rd);margin:24px 0 12px">❌ ${notWired.length} Non Wirées (Références)</h3><div class="grid">`;
for(const [n,a] of notWired){
h+=`<div class="card" style="opacity:.6">
<div class="card-h"><div class="card-n"><a href="${a.url||'#'}" target="_blank">${a.icon||''} ${n}</a></div><span class="tag tag-rd">❌</span></div>
<div class="card-row"><span class="card-k">Score</span><span class="card-v sc">${a.avg}/90</span></div>
<div class="card-row"><span class="card-k">Coût</span><span class="card-v">${a.cost||'💰'}</span></div>
</div>`;
}
h+=`</div>`;
}
$('p0').innerHTML=h;
}
function renderVsOpus(){
const A=BENCH.all_ais||{};
const weval=A.WEVAL_COMBINED||{avg:93};
const opus=A.Claude_Opus_4_6||A['Claude_Opus_4.6']||{avg:90};
const sov=Object.entries(A).filter(([n,a])=>a.wired&&a.type==='sovereign');
const agents=Object.entries(A).filter(([n,a])=>a.wired&&a.type==='agent');
const pct=Math.round(weval.avg/opus.avg*100);
let h=`<div class="opus-bar">
<span style="font-size:24px">🌟</span>
<div><div style="font-weight:900;font-size:18px">WEVAL Combined: <span style="color:var(--gn)">${weval.avg}</span> vs Opus: <span style="color:var(--go)">${opus.avg}</span></div>
<div style="font-size:11px;color:var(--fg2)">${pct}% d'Opus · ${pct>100?'SUPÉRIEUR':'Inférieur'} à Claude Opus 4.6</div></div>
<div class="bar"><div class="bar-f" style="width:${Math.min(pct,100)}%;background:${pct>=100?'var(--gn)':'var(--go)'}"></div></div>
</div>`;
h+=`<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:16px">`;
// Avantages WEVAL
h+=`<div class="card"><div class="card-h"><span class="card-n" style="color:var(--gn)">✅ Avantages WEVAL</span></div>
<div class="card-row"><span class="card-k">Coût</span><span class="card-v" style="color:var(--gn)">0€/mois</span></div>
<div class="card-row"><span class="card-k">Providers</span><span class="card-v">${sov.length} sovereign cascade</span></div>
<div class="card-row"><span class="card-k">Agents</span><span class="card-v">${agents.length} autonomes</span></div>
<div class="card-row"><span class="card-k">Failover</span><span class="card-v">Auto cascade 7 niveaux</span></div>
<div class="card-row"><span class="card-k">Souveraineté</span><span class="card-v">100% (Ollama local)</span></div>
<div class="card-row"><span class="card-k">RGPD</span><span class="card-v">EU data residency (Mistral)</span></div>
<div class="card-row"><span class="card-k">Consensus</span><span class="card-v">MoA 4 providers · 9.8/10</span></div>
</div>`;
// Avantages Opus
h+=`<div class="card"><div class="card-h"><span class="card-n" style="color:var(--go)">🏆 Avantages Opus</span></div>
<div class="card-row"><span class="card-k">Raisonnement</span><span class="card-v">Meilleur single-model</span></div>
<div class="card-row"><span class="card-k">Code</span><span class="card-v">Analyse complexe supérieure</span></div>
<div class="card-row"><span class="card-k">Coût</span><span class="card-v" style="color:var(--rd)">~$200/mois API</span></div>
<div class="card-row"><span class="card-k">Dépendance</span><span class="card-v" style="color:var(--rd)">Cloud US · Anthropic</span></div>
<div class="card-row"><span class="card-k">Rate limit</span><span class="card-v" style="color:var(--rd)">Oui</span></div>
<div class="card-row"><span class="card-k">Failover</span><span class="card-v" style="color:var(--rd)">Aucun</span></div>
<div class="card-row"><span class="card-k">Souveraineté</span><span class="card-v" style="color:var(--rd)">0%</span></div>
</div></div>`;
// Économies
h+=`<div class="kpis" style="margin-top:16px">
<div class="kpi"><div class="kpi-v" style="color:var(--gn)">$0</div><div class="kpi-l">COÛT MENSUEL</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--rd)">$200</div><div class="kpi-l">OPUS API/MOIS</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--gn)">$2,400</div><div class="kpi-l">ÉCONOMIE/AN</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--go)">${pct}%</div><div class="kpi-l">% D'OPUS</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--vi)">7</div><div class="kpi-l">CASCADE LEVELS</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--gn)">100%</div><div class="kpi-l">SOUVERAIN</div></div>
</div>`;
$('p1').innerHTML=h;
}
function renderOSS(){
const R=OSS.report||{};const S=OSS.skills||{};
const tools=R.total||70;const skills=S.total||1935;
let h=`<div class="kpis">
<div class="kpi"><div class="kpi-v" style="color:var(--bl)">${tools}</div><div class="kpi-l">TOOLS WIRED</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--vi)">${skills}</div><div class="kpi-l">SKILLS</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--gn)">${R.wire_pct||100}%</div><div class="kpi-l">WIRE RATE</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--cy)">${R.not_wired||0}</div><div class="kpi-l">NOT WIRED</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--go)">${R.categories||12}</div><div class="kpi-l">CATEGORIES</div></div>
<div class="kpi"><div class="kpi-v" style="color:var(--gn)">0€</div><div class="kpi-l">COÛT</div></div>
</div>`;
h+=`<p style="color:var(--fg2);font-size:12px;margin-bottom:12px">Données depuis <a href="/oss-discovery.html" target="_blank" style="color:var(--bl)">/oss-discovery.html</a></p>`;
const items=R.by_status||OSS.tools||[];
if(Array.isArray(items)&&items.length){
h+=`<div class="grid">`;
for(const t of items.slice(0,20)){
h+=`<div class="card"><div class="card-h"><div class="card-n">${t.name||t}</div><span class="tag tag-gn">wired</span></div></div>`;
}
h+=`</div>`;
}
$('p2').innerHTML=h;
}
function renderComparison(){
const A=BENCH.all_ais||{};
const lb=(BENCH.leaderboard||[]).sort((a,b)=>b.score-a.score);
let h=`<div style="overflow-x:auto"><table>
<tr><th>#</th><th>AI</th><th>Type</th><th>Score</th><th>%Opus</th><th>Wired</th><th>Coût</th><th>Utilisé dans</th><th>Cas d'usage</th><th>Lien</th></tr>`;
lb.forEach((x,i)=>{
const a=A[x.name]||{};
const pct=Math.round(x.score/90*100);
const wc=a.wired?'var(--gn)':'var(--rd)';
h+=`<tr>
<td>${i+1}</td>
<td style="font-weight:700">${a.icon||''} ${x.name}</td>
<td><span class="tag" style="background:rgba(255,255,255,.05)">${a.type||'?'}</span></td>
<td class="sc" style="color:${x.score>=85?'var(--gn)':x.score>=75?'var(--go)':'var(--fg3)'}">${x.score}</td>
<td>${pct}%</td>
<td style="color:${wc}">${a.wired?'✅':'❌'}</td>
<td style="font-size:9px">${a.cost||'?'}</td>
<td style="font-size:9px;max-width:150px;overflow:hidden;text-overflow:ellipsis">${a.used_in||'-'}</td>
<td style="font-size:9px;max-width:150px;overflow:hidden;text-overflow:ellipsis">${a.usecase||'-'}</td>
<td><a href="${a.url||'#'}" target="_blank" style="color:var(--bl);font-size:10px">Ouvrir →</a></td>
</tr>`;
});
h+=`</table></div>`;
$('p3').innerHTML=h;
}
load();
setInterval(load,60000);
</script>
<!-- WTP-GAP-FILL-V1 (doctrine 90-v2 gap-fill showcase, 18avr 2026) -->
<style>
.wtp-gapfill-banner{position:fixed;bottom:0;left:0;right:0;z-index:99999;background:linear-gradient(90deg,#05060a,#0b0d15 20%,#181d2e 50%,#0b0d15 80%,#05060a);border-top:2px solid #14b8a6;color:#e2e8f0;padding:10px 16px;font-family:Inter,system-ui,-apple-system,sans-serif;font-size:11.5px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;box-shadow:0 -10px 30px rgba(20,184,166,.28)}
.wtp-gapfill-banner a{color:#5eead4;text-decoration:none;font-weight:600;transition:color .15s}
.wtp-gapfill-banner a:hover{color:#22d3ee}
.wtp-gapfill-banner .pill{padding:2px 9px;background:rgba(99,102,241,.14);color:#a5b4fc;border-radius:10px;font-size:10.5px;font-family:JetBrains Mono,monospace;font-weight:600}
.wtp-gapfill-banner .pill.new{background:rgba(20,184,166,.22);color:#5eead4}
.wtp-gapfill-banner .pill.hot{background:rgba(236,72,153,.22);color:#f472b6}
.wtp-gapfill-banner .close{margin-left:auto;cursor:pointer;color:#64748b;padding:0 8px;font-size:16px;line-height:1;border:1px solid #334155;border-radius:4px}
.wtp-gapfill-banner .close:hover{color:#e2e8f0;border-color:#64748b}
.wtp-gapfill-banner.hidden{display:none}
@media(max-width:768px){.wtp-gapfill-banner{font-size:10px;padding:7px 10px;gap:8px}}
</style>
<div class="wtp-gapfill-banner" id="wtpGapFillBanner">
<span>🎯 <strong>WEVAL Agents Gap-Fill ERP</strong></span>
<span class="pill hot">47 gaps</span>
<span class="pill">SAP · Oracle · NetSuite · Dynamics</span>
<span class="pill new">🆕 Meeting Rooms</span>
<span class="pill new">🆕 Lean 6 Sigma</span>
<span id="wtp-gfb-metrics" class="pill">— chargement —</span>
<a href="/weval-technology-platform.html">→ WTP Portal (16 mod)</a>
<a href="/enterprise-model.html">Enterprise Model</a>
<a href="/api/weval-agents-gap-fill-manifest.json" target="_blank">📋 Manifest</a>
<span class="close" onclick="document.getElementById("wtpGapFillBanner").classList.add("hidden");localStorage.setItem("wtpGapFillHidden","1")">×</span>
</div>
<script>
(async()=>{
if(localStorage.getItem("wtpGapFillHidden")==="1"){document.getElementById("wtpGapFillBanner").classList.add("hidden");return;}
try{
const r=await fetch("/api/source-of-truth.json?t="+Date.now());
const d=await r.json();
const el=document.getElementById("wtp-gfb-metrics");
if(el)el.textContent=(d.ethica_total||"?")+" HCPs · "+(d.nonreg||"?")+" · "+(d.providers_count||"?")+" IA · "+(d.agents_count||"?")+" agents · "+(d.docker_running||"?")+" 🐳";
}catch(e){}
})();
</script>
<!-- WTP-D90V2-ENRICH-BANNER (doctrine 90-v2, 17avr 2026) -->
<style>
.wtp-enrich-banner{position:fixed;bottom:0;left:0;right:0;z-index:9999;background:linear-gradient(90deg,#0b0d15,#181d2e,#0b0d15);border-top:2px solid #14b8a6;color:#e2e8f0;padding:9px 18px;font-family:'JetBrains Mono',monospace,-apple-system,system-ui;font-size:11.5px;display:flex;align-items:center;gap:14px;flex-wrap:wrap;box-shadow:0 -8px 24px rgba(20,184,166,.25)}
.wtp-enrich-banner a{color:#14b8a6;text-decoration:none;font-weight:600}
.wtp-enrich-banner a:hover{color:#22d3ee}
.wtp-enrich-banner .pill{padding:2px 8px;background:rgba(99,102,241,.15);color:#a5b4fc;border-radius:10px;font-size:10.5px}
.wtp-enrich-banner .pill.new{background:rgba(20,184,166,.2);color:#5eead4}
.wtp-enrich-banner .close{margin-left:auto;cursor:pointer;color:#64748b;padding:0 6px;font-size:14px}
.wtp-enrich-banner .close:hover{color:#e2e8f0}
.wtp-enrich-banner.hidden{display:none}
@media(max-width:768px){.wtp-enrich-banner{font-size:10px;padding:6px 10px}}
</style>
<div class="wtp-enrich-banner" id="wtpEnrichBanner">
<span>🏛️ <strong>Enterprise Model 16 depts</strong></span>
<span class="pill new">🆕 Meeting Rooms</span>
<span class="pill new">🆕 Lean 6 Sigma</span>
<span id="wtp-eb-metrics" class="pill">— chargement —</span>
<a href="/weval-technology-platform.html">→ WEVAL Technology Platform (16 modules)</a>
<a href="/enterprise-model.html">Enterprise Model</a>
<a href="/wevia-master.html">WEVIA Master</a>
<span class="close" onclick="document.getElementById('wtpEnrichBanner').classList.add('hidden')">×</span>
</div>
<script>
(async()=>{try{const r=await fetch('/api/source-of-truth.json?t='+Date.now());const d=await r.json();const el=document.getElementById('wtp-eb-metrics');if(el)el.textContent=(d.ethica_total||'?')+' HCPs · '+(d.nonreg||'?')+' · '+(d.providers_count||'?')+' IA · '+(d.docker_running||'?')+' 🐳 · '+(d.subdomains_live||'?')+' subdomains';}catch(e){}})();
</script>
<!-- === 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 t33b5) --><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>