Files
html/weval-arena-v2.html
2026-04-19 22:40:02 +02:00

521 lines
27 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>WEVAL Arena v2 — Command Center IA Souveraine</title>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#06090f;--s1:#0c1018;--s2:#111827;--s3:#1a2744;
--brd:#1e2d4a;--brd2:#2a3f6a;
--t1:#e8ecf4;--t2:#8899b4;--t3:#506380;
--green:#00e09e;--red:#ff4d6a;--amber:#ffb547;
--blue:#4da6ff;--cyan:#22d3ee;--purple:#a78bfa;
--pink:#f472b6;--orange:#fb923c;
--font:'Outfit',sans-serif;--mono:'JetBrains Mono',monospace;
}
body{background:var(--bg);color:var(--t1);font-family:var(--font);overflow-x:hidden;min-height:100vh}
.app{display:grid;grid-template-columns:1fr 320px;grid-template-rows:auto 1fr auto;height:100vh;gap:0}
/* TOPBAR */
.topbar{grid-column:1/-1;background:linear-gradient(135deg,#0a0f1a 0%,#111b2e 100%);border-bottom:1px solid var(--brd);padding:12px 24px;display:flex;align-items:center;gap:16px}
.topbar .logo{font-size:22px;font-weight:800;letter-spacing:1px;background:linear-gradient(135deg,var(--cyan),var(--green));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.topbar .logo span{font-weight:400;font-size:14px;opacity:.6;-webkit-text-fill-color:var(--t2)}
.topbar .score{margin-left:auto;display:flex;gap:16px;font-family:var(--mono);font-size:12px}
.topbar .score .item{display:flex;align-items:center;gap:6px}
.topbar .score .dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 8px var(--green)}
.topbar .score .val{color:var(--green);font-weight:600}
.topbar .btn-logout{background:transparent;border:1px solid var(--brd);color:var(--t2);padding:6px 14px;border-radius:8px;cursor:pointer;font-size:12px;transition:.2s}
.topbar .btn-logout:hover{border-color:var(--cyan);color:var(--cyan)}
/* MAIN */
.main{overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:20px}
/* PROVIDER GRID */
.section-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:2px;color:var(--t3);margin-bottom:8px;display:flex;align-items:center;gap:8px}
.section-title::after{content:'';flex:1;height:1px;background:var(--brd)}
.providers-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
.prov-card{background:var(--s1);border:1px solid var(--brd);border-radius:12px;padding:14px;cursor:pointer;transition:all .25s;position:relative;overflow:hidden}
.prov-card:hover{border-color:var(--cyan);transform:translateY(-2px);box-shadow:0 8px 24px rgba(34,211,238,.08)}
.prov-card.active{border-color:var(--green);background:rgba(0,224,158,.05)}
.prov-card .prov-icon{font-size:24px;margin-bottom:8px}
.prov-card .prov-name{font-size:13px;font-weight:600;margin-bottom:2px}
.prov-card .prov-type{font-size:10px;color:var(--t3);font-family:var(--mono)}
.prov-card .prov-badge{position:absolute;top:8px;right:8px;font-size:9px;padding:2px 6px;border-radius:4px;font-weight:600;font-family:var(--mono)}
.prov-card .prov-badge.free{background:rgba(0,224,158,.15);color:var(--green)}
.prov-card .prov-badge.api{background:rgba(77,166,255,.15);color:var(--blue)}
/* CONSENSUS ROW */
.consensus-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.consensus-card{background:linear-gradient(135deg,rgba(167,139,250,.08),rgba(34,211,238,.05));border:1px solid rgba(167,139,250,.25);border-radius:12px;padding:16px;cursor:pointer;transition:.25s}
.consensus-card:hover{border-color:var(--purple);box-shadow:0 4px 20px rgba(167,139,250,.1)}
.consensus-card .cc-title{font-size:14px;font-weight:700;display:flex;align-items:center;gap:8px}
.consensus-card .cc-desc{font-size:11px;color:var(--t2);margin-top:4px}
/* MODES */
.modes-bar{display:flex;gap:6px;flex-wrap:wrap}
.mode-btn{background:var(--s1);border:1px solid var(--brd);border-radius:8px;padding:8px 14px;font-size:12px;color:var(--t2);cursor:pointer;transition:.2s;font-family:var(--font)}
.mode-btn:hover{border-color:var(--cyan);color:var(--t1)}
.mode-btn.active{background:rgba(34,211,238,.1);border-color:var(--cyan);color:var(--cyan)}
/* CHAT */
.chat-area{flex:1;min-height:200px;overflow-y:auto;display:flex;flex-direction:column;gap:12px;padding:8px 0}
.msg{max-width:85%;padding:12px 16px;border-radius:12px;font-size:14px;line-height:1.5;animation:fadeIn .3s}
.msg.user{background:var(--s3);align-self:flex-end;border-bottom-right-radius:4px}
.msg.ai{background:var(--s1);border:1px solid var(--brd);align-self:flex-start;border-bottom-left-radius:4px}
.msg .provider-tag{font-size:10px;color:var(--cyan);font-family:var(--mono);margin-top:6px;opacity:.7}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
/* INPUT */
.input-bar{display:flex;gap:8px;padding:16px 0 0}
.input-bar textarea{flex:1;background:var(--s1);border:1px solid var(--brd);border-radius:12px;padding:14px 16px;color:var(--t1);font-size:14px;font-family:var(--font);resize:none;height:52px;transition:.2s}
.input-bar textarea:focus{outline:none;border-color:var(--cyan);box-shadow:0 0 0 3px rgba(34,211,238,.1)}
.input-bar textarea::placeholder{color:var(--t3)}
.input-bar .send-btn{width:52px;height:52px;background:linear-gradient(135deg,var(--cyan),var(--green));border:none;border-radius:12px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:.2s}
.input-bar .send-btn:hover{transform:scale(1.05);box-shadow:0 4px 16px rgba(34,211,238,.3)}
.input-bar .send-btn svg{width:20px;height:20px;fill:#06090f}
/* SIDEBAR */
.sidebar{background:var(--s1);border-left:1px solid var(--brd);padding:20px;overflow-y:auto;display:flex;flex-direction:column;gap:16px}
.sidebar .stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.stat-box{background:var(--s2);border:1px solid var(--brd);border-radius:10px;padding:12px;text-align:center}
.stat-box .val{font-size:22px;font-weight:700;font-family:var(--mono)}
.stat-box .val.green{color:var(--green)}
.stat-box .val.cyan{color:var(--cyan)}
.stat-box .val.purple{color:var(--purple)}
.stat-box .val.amber{color:var(--amber)}
.stat-box .label{font-size:10px;color:var(--t3);text-transform:uppercase;letter-spacing:1px;margin-top:2px}
.cascade-list{display:flex;flex-direction:column;gap:4px}
.cascade-item{display:flex;align-items:center;gap:8px;padding:6px 0;font-size:12px}
.cascade-item .c-dot{width:6px;height:6px;border-radius:50%;background:var(--green);flex-shrink:0}
.cascade-item .c-name{color:var(--t2)}
.cascade-item.active .c-name{color:var(--t1);font-weight:500}
/* ADVANCED DROPDOWN */
.advanced-toggle{background:var(--s2);border:1px solid var(--brd);border-radius:8px;padding:8px 12px;font-size:11px;color:var(--t3);cursor:pointer;display:flex;align-items:center;gap:6px;width:100%;transition:.2s}
.advanced-toggle:hover{border-color:var(--cyan);color:var(--t2)}
.advanced-select{width:100%;background:var(--s2);border:1px solid var(--brd);border-radius:8px;padding:8px;color:var(--t1);font-size:11px;font-family:var(--mono);display:none;max-height:300px;overflow-y:auto}
.advanced-select.open{display:block}
.advanced-select option{padding:4px 8px}
.advanced-select optgroup{color:var(--cyan);font-style:normal}
/* SELECTED PROVIDER INDICATOR */
.selected-provider{display:flex;align-items:center;gap:8px;padding:8px 14px;background:rgba(34,211,238,.06);border:1px solid rgba(34,211,238,.2);border-radius:8px;font-size:12px}
.selected-provider .sp-icon{font-size:18px}
.selected-provider .sp-name{font-weight:600}
.selected-provider .sp-cost{margin-left:auto;color:var(--green);font-family:var(--mono);font-size:10px}
@media(max-width:900px){
.app{grid-template-columns:1fr}
.sidebar{display:none}
.providers-grid{grid-template-columns:repeat(2,1fr)}
.consensus-row{grid-template-columns:1fr}
}
</style>
</head>
<body>
<div class="app">
<!-- TOPBAR -->
<div class="topbar">
<div class="logo">WEVAL Arena <span>v2 · Command Center</span></div>
<div class="score">
<div class="item"><span class="dot"></span>NonReg <span class="val">153/153</span></div>
<div class="item"><span class="dot"></span>L99 <span class="val">957/957</span></div>
<div class="item"><span class="dot"></span>Providers <span class="val" id="provCount">8/8</span></div>
</div>
<button class="btn-logout" onclick="location.href='/login'">Logout</button>
</div>
<!-- MAIN CONTENT -->
<div class="main">
<!-- SELECTED PROVIDER -->
<div class="selected-provider" id="selectedProvider">
<span class="sp-icon">🔥</span>
<span class="sp-name">DeepSeek Chat</span>
<span class="sp-cost">0€ ILLIMITÉ</span>
</div>
<!-- WEB CHAT PROVIDERS - FREE & UNLIMITED -->
<div>
<div class="section-title">💬 Chat IA Gratuit · Illimité · 0€</div>
<div class="providers-grid">
<div class="prov-card active" data-model="deepseek-web" onclick="selectProvider(this)">
<div class="prov-icon">🔥</div>
<div class="prov-name">DeepSeek Chat</div>
<div class="prov-type">deepseek-v3 · web</div>
<span class="prov-badge free">0€</span>
</div>
<div class="prov-card" data-model="web-copilot" onclick="selectProvider(this)">
<div class="prov-icon">🤖</div>
<div class="prov-name">Copilot GPT-4o</div>
<div class="prov-type">microsoft · web</div>
<span class="prov-badge free">0€</span>
</div>
<div class="prov-card" data-model="web-meta" onclick="selectProvider(this)">
<div class="prov-icon">Ⓜ️</div>
<div class="prov-name">Meta AI Llama</div>
<div class="prov-type">meta · web</div>
<span class="prov-badge free">0€</span>
</div>
<div class="prov-card" data-model="web-qwen" onclick="selectProvider(this)">
<div class="prov-icon">💜</div>
<div class="prov-name">Qwen Chat</div>
<div class="prov-type">alibaba · web</div>
<span class="prov-badge free">0€</span>
</div>
<div class="prov-card" data-model="web-perplexity" onclick="selectProvider(this)">
<div class="prov-icon">🔍</div>
<div class="prov-name">Perplexity</div>
<div class="prov-type">search+ai · web</div>
<span class="prov-badge free">0€</span>
</div>
<div class="prov-card" data-model="web-duckduckgo" onclick="selectProvider(this)">
<div class="prov-icon">🦆</div>
<div class="prov-name">DuckDuckGo AI</div>
<div class="prov-type">privacy-first · web</div>
<span class="prov-badge free">0€</span>
</div>
<div class="prov-card" data-model="web-lechat" onclick="selectProvider(this)">
<div class="prov-icon">🇫🇷</div>
<div class="prov-name">Le Chat Mistral</div>
<div class="prov-type">mistral.ai · web</div>
<span class="prov-badge free">0€</span>
</div>
<div class="prov-card" data-model="web-huggingchat" onclick="selectProvider(this)">
<div class="prov-icon">🤗</div>
<div class="prov-name">HuggingChat</div>
<div class="prov-type">huggingface · web</div>
<span class="prov-badge free">0€</span>
</div>
</div>
</div>
<!-- CONSENSUS + MULTI-AGENT -->
<div>
<div class="section-title">⚖️ Consensus & Multi-Agent</div>
<div class="consensus-row">
<div class="consensus-card" data-model="consensus" onclick="selectProvider(this)">
<div class="cc-title">⚖️ Consensus MoA</div>
<div class="cc-desc">4 providers votent · meilleure réponse sélectionnée</div>
</div>
<div class="consensus-card" data-model="multiagent-all" onclick="selectProvider(this)">
<div class="cc-title">🤖 Multi-Agent ALL</div>
<div class="cc-desc">Tous les providers actifs en parallèle</div>
</div>
</div>
</div>
<!-- API PROVIDERS -->
<div>
<div class="section-title">⚡ API Sovereign · 0€ Cascade</div>
<div class="providers-grid">
<div class="prov-card" data-model="cerebras-fast" onclick="selectProvider(this)">
<div class="prov-icon"></div>
<div class="prov-name">Cerebras Fast</div>
<div class="prov-type">llama-4-scout · api</div>
<span class="prov-badge api">API</span>
</div>
<div class="prov-card" data-model="groq-llama" onclick="selectProvider(this)">
<div class="prov-icon">🟢</div>
<div class="prov-name">Groq</div>
<div class="prov-type">llama-3.1 · fastest</div>
<span class="prov-badge api">API</span>
</div>
<div class="prov-card" data-model="nim-deepseek" onclick="selectProvider(this)">
<div class="prov-icon">🟩</div>
<div class="prov-name">NVIDIA NIM</div>
<div class="prov-type">deepseek-r1 · api</div>
<span class="prov-badge api">API</span>
</div>
<div class="prov-card" data-model="mistral" onclick="selectProvider(this)">
<div class="prov-icon">🔵</div>
<div class="prov-name">Mistral</div>
<div class="prov-type">mistral-small · api</div>
<span class="prov-badge api">API</span>
</div>
</div>
</div>
<!-- MODES -->
<div>
<div class="section-title">🎯 Modes</div>
<div class="modes-bar">
<button class="mode-btn active" data-mode="think-search" onclick="selectMode(this)">⚡ Think+Search</button>
<button class="mode-btn" data-mode="deepthink" onclick="selectMode(this)">🧠 DeepThink</button>
<button class="mode-btn" data-mode="search" onclick="selectMode(this)">🔍 Search</button>
<button class="mode-btn" data-mode="expert" onclick="selectMode(this)">🎓 Expert</button>
<button class="mode-btn" data-mode="code" onclick="selectMode(this)">💻 Code</button>
<button class="mode-btn" data-mode="creative" onclick="selectMode(this)">✨ Creative</button>
</div>
</div>
<!-- CHAT AREA -->
<div class="chat-area" id="chatArea"></div>
<!-- INPUT -->
<div class="input-bar">
<textarea id="msgInput" placeholder="Message WEVAL Arena... (Enter pour envoyer)" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMsg()}"></textarea>
<button class="send-btn" onclick="sendMsg()">
<svg viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
</button>
</div>
<!-- ADVANCED -->
<details style="margin-top:8px">
<summary class="advanced-toggle">📂 405 options avancées (Hubs, Skills, Arsenal, OSS, GPU...)</summary>
<select class="advanced-select open" id="advancedSelect" onchange="selectAdvanced(this)" size="8">
<optgroup label="🔥 WEBCHAT 0€ ILLIMITÉ">
<option value="deepseek-web">🔥 DeepSeek Chat</option>
<option value="deepseek-web-think">🧠 DS DeepThink</option>
<option value="deepseek-web-search">🔍 DS Search</option>
</optgroup>
<optgroup label="⚡ API SOVEREIGN 0€">
<option value="auto">🏆 Auto (meilleur disponible)</option>
<option value="cerebras-fast">⚡ Cerebras Fast</option>
<option value="groq-llama">🟢 Groq 70B</option>
<option value="nim-deepseek">🟩 NVIDIA NIM DeepSeek</option>
</optgroup>
<optgroup label="⚖️ CONSENSUS">
<option value="consensus">⚖️ Consensus MoA · 4 providers</option>
<option value="multiagent-all">🤖 ALL Agents</option>
<option value="multiagent-5">🤖 5 Agents</option>
</optgroup>
</select>
</details>
</div>
<!-- SIDEBAR -->
<div class="sidebar">
<div class="section-title">📊 System Live</div>
<div class="stat-grid">
<div class="stat-box"><div class="val green">153/153</div><div class="label">NonReg</div></div>
<div class="stat-box"><div class="val cyan">957/957</div><div class="label">State</div></div>
<div class="stat-box"><div class="val purple">310</div><div class="label">Intents</div></div>
<div class="stat-box"><div class="val amber">17</div><div class="label">Docker</div></div>
</div>
<div class="section-title">🔗 Cascade Providers</div>
<div class="cascade-list" id="cascadeList">
<div class="cascade-item active"><span class="c-dot"></span><span class="c-name">Groq</span></div>
<div class="cascade-item active"><span class="c-dot"></span><span class="c-name">Cerebras</span></div>
<div class="cascade-item active"><span class="c-dot"></span><span class="c-name">SambaNova</span></div>
<div class="cascade-item active"><span class="c-dot"></span><span class="c-name">Gemini</span></div>
<div class="cascade-item active"><span class="c-dot"></span><span class="c-name">Mistral</span></div>
<div class="cascade-item active"><span class="c-dot"></span><span class="c-name">DeepSeek</span></div>
<div class="cascade-item active"><span class="c-dot"></span><span class="c-name">OpenRouter</span></div>
<div class="cascade-item active"><span class="c-dot"></span><span class="c-name">Anthropic</span></div>
<div class="cascade-item active"><span class="c-dot"></span><span class="c-name">Ollama</span></div>
<div class="cascade-item active"><span class="c-dot"></span><span class="c-name">DeepSeek Web</span></div>
</div>
<div class="section-title">⚡ Quick Actions</div>
<div style="display:flex;flex-direction:column;gap:4px">
<button class="mode-btn" style="width:100%;text-align:left" onclick="sendQuick('system status')">💚 System status</button>
<button class="mode-btn" style="width:100%;text-align:left" onclick="sendQuick('nonreg tests')">✅ NonReg tests</button>
<button class="mode-btn" style="width:100%;text-align:left" onclick="sendQuick('git push')">📦 Git push</button>
<button class="mode-btn" style="width:100%;text-align:left" onclick="sendQuick('cascade status')">🔗 Cascade status</button>
<button class="mode-btn" style="width:100%;text-align:left" onclick="sendQuick('reconcilie tout')">🔄 Réconciliation</button>
<button class="mode-btn" style="width:100%;text-align:left" onclick="sendQuick('teste tous les providers')">🧪 Test providers</button>
</div>
</div>
</div>
<script>
let currentModel = 'deepseek-web';
let currentMode = 'think-search';
let history = [];
function selectProvider(el) {
document.querySelectorAll('.prov-card,.consensus-card').forEach(c => c.classList.remove('active'));
el.classList.add('active');
currentModel = el.dataset.model;
const name = el.querySelector('.prov-name,.cc-title')?.textContent || currentModel;
const icon = el.querySelector('.prov-icon')?.textContent || '⚡';
document.getElementById('selectedProvider').innerHTML = `
<span class="sp-icon">${icon}</span>
<span class="sp-name">${name}</span>
<span class="sp-cost">0€ ILLIMITÉ</span>`;
document.getElementById('msgInput').focus();
}
function selectMode(el) {
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
el.classList.add('active');
currentMode = el.dataset.mode;
}
function selectAdvanced(sel) {
currentModel = sel.value;
document.getElementById('selectedProvider').innerHTML = `
<span class="sp-icon">⚙️</span>
<span class="sp-name">${sel.options[sel.selectedIndex].text}</span>
<span class="sp-cost">0€</span>`;
}
function addMsg(text, type, provider) {
const area = document.getElementById('chatArea');
const div = document.createElement('div');
div.className = `msg ${type}`;
div.innerHTML = text.replace(/\n/g,'<br>');
if (provider) div.innerHTML += `<div class="provider-tag">${provider}</div>`;
area.appendChild(div);
area.scrollTop = area.scrollHeight;
}
async function sendMsg() {
const input = document.getElementById('msgInput');
const msg = input.value.trim();
if (!msg) return;
input.value = '';
addMsg(msg, 'user');
history.push({role:'user',content:msg});
// Determine endpoint
const webModels = ['deepseek-web','deepseek-web-think','deepseek-web-search','web-copilot','web-meta','web-qwen','web-perplexity','web-duckduckgo','web-lechat','web-huggingchat'];
const ep = webModels.includes(currentModel) ? '/api/wevia-webchat-direct.php' : '/api/wevia-multi-provider.php';
try {
const res = await fetch(ep, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify(webModels.includes(currentModel) ? {service:currentModel.replace('web-',''), message:msg} : {message:msg, mode:currentMode, model:currentModel, history:history.slice(-12)})
});
/* HTML_GUARD_V2_BATCH */ const _t_d=await res.text(); const d=null; {var _q=(_t_d||"").trim();if(_q.startsWith("<!DOCTYPE")||_q.startsWith("<html")){d={error:"[HTTP "+(res.status||"?")+"] Backend indisponible",isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:"[JSON] "+e.message}}}}
const content = d.content || d.response || 'Pas de réponse';
const prov = d.provider || currentModel;
addMsg(content, 'ai', `${prov} · ${d.cost || '0€'}`);
history.push({role:'assistant',content});
} catch(e) {
addMsg('Erreur de connexion', 'ai', 'error');
}
}
function sendQuick(text) {
document.getElementById('msgInput').value = text;
sendMsg();
}
</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 === -->
<!-- === OPUS HONEST NR/L99 OVERLAY v1 19avr - append-only doctrine #14 === -->
<script>
(function(){
if (window.__opusHonestOverlay) return; window.__opusHonestOverlay = true;
async function updateHonestValues(){
try {
const r = await fetch('/api/l99-honest.php', {cache:'no-store'});
const d = await r.json();
if (!d.ok) return;
const realNR = `${d.combined.pass}/${d.combined.total}`;
const realSigma = d.sigma;
// Find elements showing the myth values
const mythRegex = /(153\/153|304\/304|NR status 153\/153|L99 status 304\/304|NR 153\/153|L99 304\/304)/g;
// Walk text nodes
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
const toReplace = [];
let node;
while (node = walker.nextNode()) {
if (node.nodeValue && mythRegex.test(node.nodeValue)) toReplace.push(node);
}
toReplace.forEach(textNode => {
const parent = textNode.parentNode;
if (!parent || parent.hasAttribute('data-opus-honest-applied')) return;
const newText = textNode.nodeValue.replace(/153\/153/g, realNR).replace(/304\/304/g, realNR);
textNode.nodeValue = newText;
parent.setAttribute('data-opus-honest-applied', '1');
});
// Add a small badge bottom-right showing honest live status
if (!document.getElementById('opus-honest-badge')) {
const b = document.createElement('div');
b.id = 'opus-honest-badge';
b.style.cssText = 'position:fixed;bottom:12px;right:12px;background:linear-gradient(90deg,#14b8a6,#a855f7);color:#05060a;padding:6px 12px;font:10px/1.3 Inter,system-ui,sans-serif;font-weight:700;border-radius:8px;z-index:99993;box-shadow:0 4px 12px rgba(0,0,0,0.3);cursor:pointer;max-width:280px';
b.title = 'Cliquer pour détails';
b.innerHTML = `✓ NR ${realNR} · ${realSigma} live`;
b.onclick = () => {
alert(`HONEST NonReg (doctrine #4):\n\nmaster: ${d.master.pass}/${d.master.total}\nopus: ${d.opus.pass}/${d.opus.total}\ncombined: ${realNR}\nsigma: ${realSigma}\n\n${d.myth_153}\n${d.myth_304}`);
};
document.body.appendChild(b);
}
} catch(e){console.error('L99-honest fetch error:', e);}
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', updateHonestValues);
else updateHonestValues();
setInterval(updateHonestValues, 90000);
})();
</script>
<!-- === OPUS HONEST END === -->
</body>
</html>