Files
html/weval-technology-platform.html
2026-04-20 17:15:01 +02:00

2606 lines
170 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" data-theme="dark">
<head>
<!-- V86 AUTH GUARD WTP · Yacine doctrine: WTP = point entrée unique AVEC AUTHENTIFICATION -->
<script>
(function() {
if (window.__WEVAL_V86_AUTH) return;
window.__WEVAL_V86_AUTH = true;
// Dev bypass: ?dev=1 or localhost
var isDev = /[?&]dev=1/.test(location.search) || location.hostname === 'localhost' || location.hostname === '127.0.0.1';
// Quick check via async fetch (non-blocking for speed, redirects if fail)
fetch('/api/auth-check.php', { credentials: 'include', cache: 'no-store' })
.then(function(r) {
if (r.status === 200) {
// Authenticated - mark body + log
document.body && document.body.setAttribute('data-auth', 'ok');
if (window.console) console.log('[V86 Auth Guard] SSO authenticated (200)');
} else if (r.status === 401 || r.status === 403) {
if (isDev) {
if (window.console) console.warn('[V86 Auth Guard] DEV mode - 401 bypassed');
document.body && document.body.setAttribute('data-auth', 'dev');
} else {
// Show redirect banner + redirect after 2s (UX premium - pas brutal)
var banner = document.createElement('div');
banner.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:99999;background:linear-gradient(135deg,#ef4444,#f59e0b);color:#fff;padding:14px 20px;text-align:center;font:600 14px -apple-system,sans-serif;box-shadow:0 4px 20px rgba(0,0,0,.3);';
banner.innerHTML = '🔐 Authentification requise · redirection vers /login.html dans <span id="v86-countdown">2</span>s · <a href="/login.html" style="color:#fff;text-decoration:underline;font-weight:700;">cliquer ici</a>';
(document.body || document.documentElement).appendChild(banner);
var sec = 2;
var iv = setInterval(function() {
sec--;
var span = document.getElementById('v86-countdown');
if (span) span.textContent = sec;
if (sec <= 0) {
clearInterval(iv);
location.href = '/login.html?from=' + encodeURIComponent(location.pathname);
}
}, 1000);
}
} else {
// Other (500, network etc.) - log + non-blocking
if (window.console) console.warn('[V86 Auth Guard] auth-check returned', r.status);
}
})
.catch(function(e) {
// Auth-check unreachable - graceful: allow (don't break WTP if API down)
if (window.console) console.warn('[V86 Auth Guard] auth-check unreachable, allowing (graceful)', e);
});
})();
</script>
<!-- V86 AUTH GUARD WTP END -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WEVAL Technology Platform — All-in-One ERP Portal</title>
<meta name="description" content="WEVAL Technology Platform — Portail unifié ERP-like tous serveurs, toutes apps, toutes capacités">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
:root{
--bg-0:#05060a;--bg-1:#0a0c14;--bg-2:#11141f;--bg-3:#181c2b;--bg-card:#0e111c;
--border:#1f2436;--border-hover:#3a425f;
--text-0:#f1f5f9;--text-1:#cbd5e1;--text-2:#94a3b8;--text-3:#64748b;
--accent:#6366f1;--accent-hover:#818cf8;
--success:#10b981;--warning:#f59e0b;--danger:#ef4444;--info:#06b6d4;
--shadow-lg:0 16px 48px rgba(99,102,241,.2);
--radius:12px;--radius-sm:8px;
--trans:.18s cubic-bezier(.4,0,.2,1);
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{font-family:'Inter',system-ui,-apple-system,sans-serif;background:var(--bg-0);color:var(--text-0);font-size:13.5px;line-height:1.5;overflow:hidden}
a{color:inherit;text-decoration:none}
/* ===== LAYOUT ===== */
.wtp-root{display:grid;grid-template-columns:240px 1fr 380px;grid-template-rows:58px 1fr 32px;grid-template-areas:"topbar topbar topbar" "sidebar main chat" "status status status";height:100vh}
/* ===== TOP BAR ===== */
.wtp-topbar{grid-area:topbar;background:linear-gradient(90deg,var(--bg-1),var(--bg-2));border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 18px;gap:18px;z-index:100}
.wtp-brand{display:flex;align-items:center;gap:10px;font-weight:800;cursor:pointer}
.wtp-logo{width:34px;height:34px;background:linear-gradient(135deg,#6366f1,#8b5cf6,#ec4899);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:17px;font-weight:900;box-shadow:0 4px 14px rgba(99,102,241,.4)}
.wtp-brand-text{display:flex;flex-direction:column;line-height:1.1}
.wtp-brand-text .top{font-size:13px;letter-spacing:-.01em}
.wtp-brand-text .sub{font-size:9.5px;color:var(--text-3);font-weight:500;letter-spacing:.09em;margin-top:2px;text-transform:uppercase}
.wtp-search{flex:1;max-width:580px;position:relative}
.wtp-search input{width:100%;background:var(--bg-3);border:1px solid var(--border);color:var(--text-0);padding:9px 14px 9px 38px;border-radius:8px;font-size:13px;font-family:inherit;transition:var(--trans)}
.wtp-search input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(99,102,241,.13)}
.wtp-search::before{content:'⌕';position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--text-3);font-size:15px}
.wtp-search-results{position:absolute;top:100%;left:0;right:0;background:var(--bg-2);border:1px solid var(--border);border-radius:8px;max-height:420px;overflow-y:auto;margin-top:4px;display:none;z-index:200;box-shadow:var(--shadow-lg)}
.wtp-search-results.show{display:block}
.wtp-search-result{padding:9px 14px;cursor:pointer;border-bottom:1px solid var(--border);transition:var(--trans)}
.wtp-search-result:hover{background:var(--bg-3)}
.wtp-search-result .title{font-size:13px;font-weight:500;color:var(--text-0)}
.wtp-search-result .module{font-size:11px;color:var(--text-2);margin-top:2px;font-family:'JetBrains Mono',monospace}
.wtp-search-result mark{background:rgba(99,102,241,.35);color:var(--text-0);padding:0 2px;border-radius:2px}
.wtp-actions{display:flex;gap:8px;align-items:center}
.wtp-icon-btn{width:34px;height:34px;background:var(--bg-3);border:1px solid var(--border);border-radius:8px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--text-1);font-size:15px;transition:var(--trans)}
.wtp-icon-btn:hover{border-color:var(--accent);color:var(--text-0)}
.wtp-icon-btn .dot{position:absolute;top:6px;right:6px;width:7px;height:7px;background:var(--success);border-radius:50%;box-shadow:0 0 8px var(--success)}
.wtp-user{display:flex;align-items:center;gap:8px;padding:4px 12px 4px 4px;background:var(--bg-3);border:1px solid var(--border);border-radius:18px;cursor:pointer}
.wtp-avatar{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,#f59e0b,#ef4444);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:11px;color:#fff}
.wtp-user-name{font-size:12px;font-weight:600}
/* ===== SIDEBAR ===== */
.wtp-sidebar{grid-area:sidebar;background:var(--bg-1);border-right:1px solid var(--border);overflow-y:auto;padding:12px 8px}
.wtp-sidebar::-webkit-scrollbar{width:6px}
.wtp-sidebar::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
.wtp-nav-label{font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--text-3);padding:8px 10px 4px;font-weight:700}
.wtp-nav-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:7px;cursor:pointer;color:var(--text-1);font-size:12.5px;font-weight:500;transition:var(--trans);position:relative;margin-bottom:2px}
.wtp-nav-item:hover{background:var(--bg-3);color:var(--text-0)}
.wtp-nav-item.active{background:linear-gradient(90deg,rgba(99,102,241,.18),transparent);color:var(--text-0);padding-left:8px;border-left:2px solid var(--accent)}
.wtp-nav-icon{font-size:15px;width:20px;text-align:center}
.wtp-nav-count{margin-left:auto;font-size:10px;padding:1px 6px;background:var(--bg-3);border-radius:9px;color:var(--text-2);font-family:'JetBrains Mono',monospace;font-weight:700;min-width:24px;text-align:center}
.wtp-nav-item.active .wtp-nav-count{background:var(--accent);color:#fff}
/* ===== MAIN ===== */
.wtp-main{grid-area:main;overflow-y:auto;padding:18px 24px;background:var(--bg-0)}
.wtp-main::-webkit-scrollbar{width:8px}
.wtp-main::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
.wtp-header{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:14px;flex-wrap:wrap;gap:12px}
.wtp-header h1{font-size:22px;font-weight:700;letter-spacing:-.02em;display:flex;align-items:center;gap:12px}
.wtp-header h1 .module-icon{width:40px;height:40px;background:var(--bg-card);border:1px solid var(--border);border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:21px}
.wtp-header-desc{font-size:12.5px;color:var(--text-2);margin-top:4px;margin-left:52px}
.wtp-refresh{background:var(--bg-3);border:1px solid var(--border);color:var(--text-1);padding:7px 12px;border-radius:7px;cursor:pointer;font-size:11.5px;font-family:'JetBrains Mono',monospace;transition:var(--trans)}
.wtp-refresh:hover{border-color:var(--accent);color:var(--text-0)}
/* KPI strip */
.wtp-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(155px,1fr));gap:10px;margin-bottom:20px}
.wtp-kpi{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-sm);padding:11px 14px;transition:var(--trans)}
.wtp-kpi:hover{border-color:var(--accent-hover);transform:translateY(-1px)}
.wtp-kpi-label{font-size:9.5px;color:var(--text-3);text-transform:uppercase;letter-spacing:.11em;font-weight:600}
.wtp-kpi-value{font-size:21px;font-weight:700;margin-top:3px;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.wtp-kpi-value.ok{color:var(--success)}
.wtp-kpi-value.warn{color:var(--warning)}
.wtp-kpi-value.info{color:var(--info)}
.wtp-kpi-unit{font-size:11px;color:var(--text-3);font-weight:500;margin-left:4px}
/* Submodule grid (Fiori tiles) */
.wtp-section-title{font-size:11px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;color:var(--text-3);margin:12px 0 10px}
.wtp-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(290px,1fr));gap:12px}
.wtp-tile{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:var(--trans);position:relative;overflow:hidden}
.wtp-tile::before{content:'';position:absolute;top:0;left:0;width:100%;height:3px;background:var(--tile-color,var(--accent));opacity:.65;transition:opacity .2s}
.wtp-tile:hover::before{opacity:1}
.wtp-tile:hover{transform:translateY(-2px);border-color:var(--tile-color,var(--accent));box-shadow:0 8px 24px rgba(0,0,0,.35)}
.wtp-tile-head{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:6px;gap:10px}
.wtp-tile-title{font-size:13.5px;font-weight:600;color:var(--text-0);line-height:1.3}
.wtp-tile-badge{font-size:9.5px;padding:2px 7px;background:var(--bg-3);border-radius:10px;color:var(--text-2);font-family:'JetBrains Mono',monospace;white-space:nowrap;flex-shrink:0}
.wtp-tile-badge.live{background:rgba(16,185,129,.18);color:var(--success)}
.wtp-tile-badge.dormant{background:rgba(100,116,139,.2);color:var(--text-3)}
.wtp-tile-desc{font-size:11.5px;color:var(--text-2);margin-bottom:10px;line-height:1.45}
.wtp-tile-meta{display:flex;flex-wrap:wrap;gap:5px;font-size:10.5px}
.wtp-pill{padding:2px 7px;background:rgba(99,102,241,.12);color:var(--accent-hover);border-radius:6px;font-family:'JetBrains Mono',monospace;font-weight:500}
.wtp-pill.page{background:rgba(16,185,129,.12);color:var(--success)}
.wtp-pill.api{background:rgba(245,158,11,.12);color:var(--warning)}
.wtp-pill.docker{background:rgba(6,182,212,.12);color:var(--info)}
.wtp-pill.script{background:rgba(236,72,153,.12);color:#f472b6}
.wtp-pill.path{background:rgba(168,85,247,.12);color:#c084fc}
.wtp-tile-footer{margin-top:10px;display:flex;justify-content:space-between;align-items:center;padding-top:10px;border-top:1px solid var(--border)}
.wtp-tile-open{font-size:11px;color:var(--accent-hover);font-weight:600}
/* HOME view specific */
.wtp-home-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:12px;margin-bottom:20px}
.wtp-home-module{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:18px;cursor:pointer;transition:var(--trans);position:relative;overflow:hidden}
.wtp-home-module::before{content:'';position:absolute;top:0;left:0;width:4px;height:100%;background:var(--mod-color,var(--accent))}
.wtp-home-module:hover{transform:translateY(-3px);border-color:var(--mod-color,var(--accent));box-shadow:var(--shadow-lg)}
.wtp-home-icon{font-size:32px;margin-bottom:10px;display:inline-block;filter:drop-shadow(0 4px 12px rgba(99,102,241,.25))}
.wtp-home-label{font-size:15px;font-weight:700;color:var(--text-0);margin-bottom:4px}
.wtp-home-tag{font-size:11.5px;color:var(--text-2);line-height:1.45;margin-bottom:12px}
.wtp-home-stat{display:flex;gap:14px;font-size:11px}
.wtp-home-stat div{display:flex;align-items:center;gap:4px}
.wtp-home-stat .num{font-weight:700;color:var(--mod-color,var(--accent));font-family:'JetBrains Mono',monospace}
.wtp-home-stat .lbl{color:var(--text-3)}
/* ===== RIGHT CHAT ===== */
.wtp-chat{grid-area:chat;background:var(--bg-1);border-left:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden}
.wtp-chat-head{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;background:var(--bg-2)}
.wtp-chat-title{font-size:12.5px;font-weight:600;display:flex;align-items:center;gap:8px}
.wtp-chat-title .pulse{width:7px;height:7px;background:var(--success);border-radius:50%;animation:pulse 2s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
.wtp-chat-frame{flex:1;width:100%;border:0;background:var(--bg-0)}
/* ===== STATUS BAR ===== */
.wtp-statusbar{grid-area:status;background:linear-gradient(90deg,var(--bg-1),var(--bg-2));border-top:1px solid var(--border);display:flex;align-items:center;padding:0 16px;gap:20px;font-size:11px;color:var(--text-2);font-family:'JetBrains Mono',monospace}
.wtp-status-item{display:flex;align-items:center;gap:5px}
.wtp-status-item .dot{width:7px;height:7px;border-radius:50%}
.wtp-status-item .dot.ok{background:var(--success);box-shadow:0 0 6px var(--success)}
.wtp-status-item .dot.warn{background:var(--warning);box-shadow:0 0 6px var(--warning)}
.wtp-status-item .dot.err{background:var(--danger);box-shadow:0 0 6px var(--danger)}
.wtp-status-item strong{color:var(--text-0);font-weight:700}
.wtp-status-spacer{flex:1}
/* Loading */
.wtp-loading{display:flex;align-items:center;justify-content:center;height:200px;color:var(--text-3);font-size:12px}
.wtp-loading::after{content:'●';animation:dots 1.4s infinite;font-size:20px;margin-left:6px}
@keyframes dots{0%,20%{opacity:0}40%,100%{opacity:1}}
/* Responsive — hide chat on small */
@media(max-width:1280px){.wtp-root{grid-template-columns:220px 1fr 0}.wtp-chat{display:none}}
@media(max-width:900px){.wtp-root{grid-template-columns:0 1fr 0}.wtp-sidebar{display:none}}
/* ===== VISUAL MANAGEMENT PREMIUM (VISUAL-MGMT-PREMIUM-V1, doctrine 60 + L6S) ===== */
.vm-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:14px;margin-bottom:24px}
.vm-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:16px;position:relative;overflow:hidden;transition:var(--trans)}
.vm-card:hover{border-color:var(--border-hover)}
.vm-card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
.vm-card-title{font-size:10.5px;color:var(--text-2);text-transform:uppercase;letter-spacing:.7px;font-weight:600}
.vm-card-badge{font-size:9.5px;padding:2px 7px;border-radius:8px;background:rgba(16,185,129,.15);color:#5eead4;font-weight:600}
.vm-card-badge.warn{background:rgba(245,158,11,.15);color:#fbbf24}
.vm-card-badge.danger{background:rgba(239,68,68,.15);color:#fca5a5}
/* Gauges */
.vm-gauge{position:relative;width:100%;height:125px;display:flex;align-items:center;justify-content:center}
.vm-gauge svg{width:100%;height:100%;max-width:200px}
.vm-gauge-value{position:absolute;top:52%;left:50%;transform:translate(-50%,-50%);text-align:center}
.vm-gauge-num{font-size:28px;font-weight:800;background:linear-gradient(135deg,#22d3ee,#a855f7);-webkit-background-clip:text;background-clip:text;color:transparent;line-height:1}
.vm-gauge-unit{font-size:10px;color:var(--text-3);margin-top:3px;letter-spacing:.5px}
.vm-gauge-label{text-align:center;font-size:11px;color:var(--text-2);margin-top:4px;letter-spacing:.3px}
/* Andon (Lean 6 Sigma light stack) */
.vm-andon{display:flex;flex-direction:column;gap:8px;align-items:center;padding:14px 0}
.vm-light{width:34px;height:34px;border-radius:50%;border:2px solid rgba(255,255,255,.06);position:relative;transition:var(--trans)}
.vm-light.on.green{background:radial-gradient(circle at 30% 30%,#6ee7b7,#10b981);box-shadow:0 0 20px #10b981,0 0 40px #10b981aa;animation:vm-pulse 2s infinite}
.vm-light.on.yellow{background:radial-gradient(circle at 30% 30%,#fde68a,#f59e0b);box-shadow:0 0 20px #f59e0b,0 0 40px #f59e0baa;animation:vm-pulse 1.4s infinite}
.vm-light.on.red{background:radial-gradient(circle at 30% 30%,#fca5a5,#ef4444);box-shadow:0 0 20px #ef4444,0 0 40px #ef4444aa;animation:vm-pulse .9s infinite}
.vm-light.off{background:var(--bg-3)}
.vm-light-label{font-size:9.5px;color:var(--text-3);text-align:center;margin-top:2px;font-family:'JetBrains Mono',monospace}
@keyframes vm-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.07)}}
/* Heatmap */
.vm-heat{display:grid;grid-template-columns:repeat(12,1fr);gap:2px;margin-top:8px}
.vm-heat-cell{aspect-ratio:1;border-radius:2px;background:var(--bg-3);position:relative;cursor:help;transition:var(--trans)}
.vm-heat-cell:hover{transform:scale(1.3);z-index:10;border:1px solid var(--text-0)}
.vm-heat-cell[data-v="0"]{background:#1f2436}
.vm-heat-cell[data-v="1"]{background:rgba(16,185,129,.15)}
.vm-heat-cell[data-v="2"]{background:rgba(16,185,129,.35)}
.vm-heat-cell[data-v="3"]{background:rgba(16,185,129,.6)}
.vm-heat-cell[data-v="4"]{background:rgba(16,185,129,1)}
.vm-heat-cell[data-v="w"]{background:rgba(245,158,11,.6)}
.vm-heat-cell[data-v="r"]{background:rgba(239,68,68,.8)}
.vm-heat-legend{display:flex;gap:6px;font-size:9.5px;color:var(--text-3);margin-top:6px;align-items:center}
.vm-heat-legend span{display:inline-block;width:10px;height:10px;border-radius:2px}
/* Sparkline */
.vm-spark{width:100%;height:54px;display:block}
.vm-spark path.line{fill:none;stroke:url(#grad-spark);stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round}
.vm-spark path.area{fill:url(#grad-spark-area);opacity:.35}
/* Progress ring for L6S Cp/Cpk */
.vm-ring{position:relative;width:100%;height:100%}
.vm-ring svg{transform:rotate(-90deg)}
.vm-ring-text{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center}
/* Bar compare (ACQUIS vs DORMANTS) */
.vm-bars{display:flex;flex-direction:column;gap:6px;margin-top:6px}
.vm-bar-row{display:grid;grid-template-columns:75px 1fr 42px;gap:8px;align-items:center;font-size:11px}
.vm-bar-label{color:var(--text-1);font-weight:500}
.vm-bar-track{height:8px;background:var(--bg-3);border-radius:4px;overflow:hidden;position:relative}
.vm-bar-fill{height:100%;border-radius:4px;transition:width 1.2s cubic-bezier(.4,0,.2,1);background:linear-gradient(90deg,#10b981,#06b6d4)}
.vm-bar-fill.warn{background:linear-gradient(90deg,#f59e0b,#ef4444)}
.vm-bar-count{text-align:right;color:var(--text-1);font-weight:600;font-family:'JetBrains Mono',monospace}
/* Kanban flow (L6S value stream) */
.vm-flow{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-top:8px}
.vm-flow-step{text-align:center;padding:7px 4px;background:var(--bg-3);border-radius:6px;position:relative;font-size:10px;color:var(--text-2)}
.vm-flow-step.done{background:rgba(16,185,129,.2);color:#6ee7b7}
.vm-flow-step.work{background:rgba(99,102,241,.2);color:#a5b4fc;animation:vm-pulse 2s infinite}
.vm-flow-step.block{background:rgba(239,68,68,.2);color:#fca5a5}
.vm-flow-step .num{display:block;font-size:16px;font-weight:800;margin-bottom:2px}
/* Coverage donut */
.vm-donut-wrap{display:flex;align-items:center;gap:16px}
.vm-donut{position:relative;width:110px;height:110px;flex-shrink:0}
.vm-donut svg{transform:rotate(-90deg)}
.vm-donut-text{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center}
.vm-donut-big{font-size:24px;font-weight:800;color:var(--text-0)}
.vm-donut-lbl{font-size:9px;color:var(--text-3);letter-spacing:.5px;text-transform:uppercase}
.vm-donut-list{flex:1;display:flex;flex-direction:column;gap:5px;font-size:10.5px}
.vm-donut-list div{display:flex;justify-content:space-between;align-items:center}
.vm-donut-list .dot{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:6px}
/* Scorecard (Andon KPI) */
.vm-score{text-align:center;padding:6px 0}
.vm-score-big{font-size:32px;font-weight:800;line-height:1}
.vm-score-big.ok{color:#10b981}
.vm-score-big.warn{color:#f59e0b}
.vm-score-big.danger{color:#ef4444}
.vm-score-sub{font-size:10.5px;color:var(--text-3);margin-top:4px;letter-spacing:.3px}
/* Span utilities */
.vm-col-4{grid-column:span 4}.vm-col-3{grid-column:span 3}.vm-col-6{grid-column:span 6}.vm-col-8{grid-column:span 8}.vm-col-12{grid-column:span 12}
@media(max-width:1280px){.vm-col-4{grid-column:span 6}.vm-col-3{grid-column:span 6}}
/* Pulse live indicator */
.vm-live{display:inline-flex;align-items:center;gap:5px;font-size:9.5px;color:#10b981}
.vm-live::before{content:'';width:6px;height:6px;background:#10b981;border-radius:50%;box-shadow:0 0 6px #10b981;animation:vm-pulse 1.5s infinite}
/* Hide old static kpi cards when VM active */
.vm-active .wtp-kpis{display:none}
/* ===== V64-DEPTS-KPI-BESTPRACTICES (SAP SAFe PMI L6S) ===== */
.v64-dept-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-top:6px}
@media(max-width:1280px){.v64-dept-grid{grid-template-columns:repeat(3,1fr)}}
@media(max-width:768px){.v64-dept-grid{grid-template-columns:repeat(2,1fr)}}
.v64-dept{background:var(--bg-3);border-radius:8px;padding:9px 10px;border-left:3px solid var(--dcol,#6366f1);position:relative;transition:var(--trans);cursor:default}
.v64-dept:hover{background:var(--bg-2);transform:translateY(-1px)}
.v64-dept-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
.v64-dept-name{font-size:11px;font-weight:600;color:var(--text-0);display:flex;align-items:center;gap:5px}
.v64-dept-sap{font-size:8.5px;color:var(--text-3);font-family:'JetBrains Mono',monospace;padding:1px 5px;background:rgba(99,102,241,.1);border-radius:3px}
.v64-dept-kpis{display:grid;grid-template-columns:repeat(2,1fr);gap:4px;font-size:9.5px}
.v64-dept-kpi{background:var(--bg-1);padding:4px 6px;border-radius:4px;position:relative}
.v64-dept-kpi .l{color:var(--text-3);font-size:9px;letter-spacing:.2px;display:block;margin-bottom:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.v64-dept-kpi .v{color:var(--text-0);font-weight:700;font-size:10.5px;font-family:'JetBrains Mono',monospace}
.v64-dept-kpi .t{color:var(--text-3);font-size:8.5px;font-family:'JetBrains Mono',monospace}
.v64-dept-kpi.ok{border-left:2px solid #10b981}
.v64-dept-kpi.warn{border-left:2px solid #f59e0b}
.v64-dept-kpi.critical{border-left:2px solid #ef4444}
.v64-dept-kpi.critical .v{color:#fca5a5}
.v64-dept-kpi.warn .v{color:#fbbf24}
.v64-dept-kpi.ok .v{color:#6ee7b7}
.v64-dept-agents{display:flex;align-items:center;gap:6px;margin-top:6px;font-size:9px;color:var(--text-3)}
.v64-dept-agents .pct-bar{flex:1;height:3px;background:var(--bg-0);border-radius:2px;overflow:hidden}
.v64-dept-agents .pct-fill{height:100%;background:linear-gradient(90deg,#10b981,#06b6d4);transition:width 1s cubic-bezier(.4,0,.2,1)}
/* Best practices */
.v64-bp-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-top:8px}
@media(max-width:1280px){.v64-bp-grid{grid-template-columns:repeat(2,1fr)}}
.v64-bp{background:var(--bg-3);border-radius:8px;padding:12px;position:relative}
.v64-bp-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.v64-bp-title{font-size:11px;font-weight:600;color:var(--text-0);display:flex;align-items:center;gap:6px}
.v64-bp-maturity{font-size:13px;font-weight:800;font-family:'JetBrains Mono',monospace}
.v64-bp-maturity.ok{color:#10b981}
.v64-bp-maturity.warn{color:#f59e0b}
.v64-bp-maturity.low{color:#ef4444}
.v64-bp-ring{width:100%;height:46px;margin-bottom:10px;display:flex;align-items:center;gap:10px}
.v64-bp-ring-bar{flex:1;height:6px;background:var(--bg-0);border-radius:3px;overflow:hidden;position:relative}
.v64-bp-ring-fill{height:100%;background:linear-gradient(90deg,#ef4444 0%,#f59e0b 40%,#10b981 80%);transition:width 1.2s cubic-bezier(.4,0,.2,1)}
.v64-bp-principles{display:flex;flex-direction:column;gap:4px;font-size:10px}
.v64-bp-p{display:flex;align-items:center;justify-content:space-between;padding:3px 6px;background:var(--bg-1);border-radius:4px}
.v64-bp-p-label{color:var(--text-1);flex:1}
.v64-bp-p-status{font-size:9px;padding:1px 5px;border-radius:8px;font-weight:600;margin-left:4px;white-space:nowrap}
.v64-bp-p-status.ok{background:rgba(16,185,129,.18);color:#6ee7b7}
.v64-bp-p-status.partial{background:rgba(245,158,11,.18);color:#fbbf24}
.v64-bp-p-status.missing{background:rgba(239,68,68,.18);color:#fca5a5}
/* Gaps list */
.v64-gaps{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;max-height:260px;overflow-y:auto;padding-right:4px}
@media(max-width:1280px){.v64-gaps{grid-template-columns:repeat(2,1fr)}}
@media(max-width:768px){.v64-gaps{grid-template-columns:1fr}}
.v64-gap{background:var(--bg-3);border-radius:6px;padding:7px 10px;border-left:2.5px solid #ef4444;display:flex;align-items:center;justify-content:space-between;font-size:10px;gap:8px}
.v64-gap:hover{background:var(--bg-2)}
.v64-gap-name{color:var(--text-0);font-weight:600;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.v64-gap-dept{color:var(--text-3);font-family:'JetBrains Mono',monospace;font-size:8.5px;padding:1px 5px;background:rgba(99,102,241,.12);border-radius:3px;white-space:nowrap}
.v64-gaps::-webkit-scrollbar{width:6px}
.v64-gaps::-webkit-scrollbar-track{background:var(--bg-0)}
.v64-gaps::-webkit-scrollbar-thumb{background:var(--bg-3);border-radius:3px}
/* V73-FIORI-TILES - SAP S/4HANA Fiori-inspired sub-module rendering */
.wtp-tile{display:flex;flex-direction:column;min-height:152px}
.wtp-tile-icon{font-size:22px;line-height:1;margin-bottom:10px;opacity:0.85}
.wtp-tile:hover .wtp-tile-icon{opacity:1;transform:scale(1.1);transition:transform .2s}
.wtp-tile-title{font-weight:700 !important}
.wtp-tile-meta .wtp-pill{transition:all .15s}
.wtp-tile-meta .wtp-pill:hover{transform:translateY(-1px);box-shadow:0 2px 8px rgba(99,102,241,0.25)}
/* V73 new subfooter */
.wtp-tile-subfooter{margin-top:auto;padding-top:10px;border-top:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;font-size:10.5px;color:var(--text-3)}
.wtp-tile-subfooter .kpi-mini{font-family:'JetBrains Mono',monospace;color:var(--success);font-weight:700}
</style>
</head>
<body>
<div class="wtp-root">
<!-- TOP BAR -->
<div class="wtp-topbar">
<div class="wtp-brand" onclick="navigateTo('home')">
<div class="wtp-logo">W</div>
<div class="wtp-brand-text">
<div class="top">WEVAL Technology</div>
<div class="sub">Platform · ERP Portal</div>
</div>
</div>
<div class="wtp-search">
<input type="text" id="wtp-search-input" placeholder="Rechercher page, API, app, module, agent…" autocomplete="off">
<div class="wtp-search-results" id="wtp-search-results"></div>
</div>
<div class="wtp-actions">
<div class="wtp-icon-btn" title="Notifications" onclick="showNotifications()" style="position:relative">🔔<span class="dot"></span></div>
<div class="wtp-icon-btn" title="Help" onclick="openUrl('/wiki/')">?</div>
<button id="v80-toggle-inline" onclick="v80Open()" title="Navigation Archi Unifiee - toutes les portes" style="background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;border:none;padding:8px 14px;border-radius:18px;cursor:pointer;font-size:12px;font-weight:700;letter-spacing:.3px;display:flex;align-items:center;gap:6px;transition:all .2s;box-shadow:0 4px 12px rgba(99,102,241,.3)" onmouseover="this.style.transform='translateY(-1px)';this.style.boxShadow='0 6px 18px rgba(99,102,241,.5)'" onmouseout="this.style.transform='translateY(0)';this.style.boxShadow='0 4px 12px rgba(99,102,241,.3)'"><span style="font-size:14px">🧭</span>Archi complete</button>
<div class="wtp-user">
<div class="wtp-avatar">YM</div>
<span class="wtp-user-name">Yacine M.</span>
</div>
</div>
</div>
<!-- LEFT SIDEBAR -->
<div class="wtp-sidebar" id="wtp-sidebar">
<div class="wtp-nav-label">Portail</div>
<div class="wtp-nav-item active" data-mod="home" onclick="navigateTo('home')">
<span class="wtp-nav-icon">🏠</span><span>Accueil</span>
</div>
<div class="wtp-nav-label">Modules ERP</div>
<div id="wtp-nav-modules"></div>
</div>
<!-- MAIN AREA -->
<div class="wtp-main" id="wtp-main">
<div class="wtp-loading">Chargement de la plateforme</div>
</div>
<!-- RIGHT CHAT (WEVIA Master) -->
<div class="wtp-chat">
<div class="wtp-chat-head">
<div class="wtp-chat-title"><span class="pulse"></span>WEVIA Master · multi-agents</div>
<div class="wtp-icon-btn" title="Ouvrir en plein écran" onclick="openUrl('/wevia-master.html')" style="width:28px;height:28px;font-size:12px"></div>
</div>
<iframe class="wtp-chat-frame" src="/wevia-widget.html" title="WEVIA Master chat"></iframe>
</div>
<!-- STATUS BAR -->
<div class="wtp-statusbar" id="wtp-statusbar">
<div class="wtp-status-item"><span class="dot ok"></span>S204 <strong>live</strong></div>
<div class="wtp-status-item"><span class="dot ok"></span>S95 <strong>live</strong></div>
<div class="wtp-status-item" id="st-blade-wrap"><span class="dot warn" id="st-blade-dot"></span><span id="st-blade-label">Blade <strong id="st-blade-text">checking</strong></span></div>
<div class="wtp-status-item">Docker <strong id="st-docker"></strong></div>
<div class="wtp-status-item">Providers <strong id="st-prov"></strong></div>
<div class="wtp-status-item">Qdrant <strong id="st-qdrant"></strong></div>
<div class="wtp-status-item">NonReg <strong id="st-nr"></strong></div>
<div class="wtp-status-item">HCPs <strong id="st-hcp"></strong></div>
<div class="wtp-status-spacer"></div>
<div class="wtp-status-item">WTP <strong id="st-ver">v2.0</strong></div>
<div class="wtp-status-item" id="st-time"></div>
</div>
</div>
<script>
let TREE = null;
let CURRENT_MOD = 'home';
const COLORS = {intelligence:'#6366f1',commerce:'#10b981',finance:'#f97316',marketing:'#f59e0b',growth:'#ec4899',hr:'#0ea5e9',supply:'#84cc16',operations:'#8b5cf6',erp_integrations:'#dc2626',communications:'#f43f5e',security:'#6b7280',development:'#06b6d4',knowledge:'#0d9488',multimodal:'#a855f7',rnd_labs:'#7c3aed'};
// Load full tree from API
async function loadTree(){
try{
const r = await fetch('/api/weval-technology-platform-api.php');
TREE = await r.json();
renderSidebar();
renderStatusBar();
navigateTo(CURRENT_MOD);
}catch(e){
document.getElementById('wtp-main').innerHTML = '<div class="wtp-loading">Erreur chargement API: '+e.message+'</div>';
}
}
function renderSidebar(){
const el = document.getElementById('wtp-nav-modules');
if (!TREE) return;
// 16 ERP modules
let html = Object.entries(TREE.modules).map(([id, mod]) => {
const count = (mod.submodules||[]).length;
return `<div class="wtp-nav-item" data-mod="${id}" onclick="navigateTo('${id}')">
<span class="wtp-nav-icon" style="color:${mod.color}">${mod.icon}</span>
<span>${mod.label}</span>
<span class="wtp-nav-count">${count}</span>
</div>`;
}).join('');
// SECTION SÉPARATRICE · POINT D'ENTRÉE ENRICHI (Opus Yacine 19avr)
html += `<div style="padding:10px 14px 6px;font-size:10px;color:var(--wtp-muted);text-transform:uppercase;letter-spacing:1.5px;margin-top:14px;border-top:1px solid var(--wtp-border);padding-top:14px">POINT D'ENTRÉE TOTAL</div>`;
const extras = [
{id:'infra', icon:'🖥', label:'Infrastructure', color:'#22d3ee', count: (TREE.infra?.machines?.length||0) + (TREE.infra?.docker?.length||0)},
{id:'all_pages', icon:'📂', label:'Toutes les pages', color:'#d4af37', count: TREE.all_pages?.total||0},
{id:'all_apis', icon:'🔌', label:'Toutes les APIs', color:'#a855f7', count: TREE.apis?.total||0},
{id:'multiagent', icon:'🤖', label:'Multi-Agent Modes', color:'#ef4444', count: TREE.multiagent_modes?.length||0},
{id:'truth', icon:'🔒', label:'Truth Registry', color:'#22c55e', count: TREE.truth_registry?.agents||0},
{id:'crm_bridge', icon:'🔗', label:'CRM Bridge (4 CRMs)', color:'#22d3ee', count: 4},
];
html += extras.map(e => `<div class="wtp-nav-item" data-mod="${e.id}" onclick="navigateTo('${e.id}')">
<span class="wtp-nav-icon" style="color:${e.color}">${e.icon}</span>
<span>${e.label}</span>
<span class="wtp-nav-count">${e.count}</span>
</div>`).join('');
el.innerHTML = html;
}
function navigateTo(modId){
CURRENT_MOD = modId;
document.querySelectorAll('.wtp-nav-item').forEach(i => i.classList.toggle('active', i.dataset.mod === modId));
if (modId === 'home') renderHome();
else if (modId === 'infra') renderInfra();
else if (modId === 'crm_bridge') { window.open('/wevia-ia/wevia-admin-crm-v68.php', '_blank'); return; }
else if (modId === 'all_pages') renderAllPages();
else if (modId === 'all_apis') renderAllApis();
else if (modId === 'multiagent') renderMultiagent();
else if (modId === 'truth') renderTruth();
else renderModule(modId);
document.getElementById('wtp-main').scrollTop = 0;
}
// === POINT D'ENTRÉE TOTAL views (Opus Yacine 19avr) ===
function renderInfra(){
const main = document.getElementById('wtp-main');
const i = TREE.infra || {};
const machines = (i.machines||[]).map(m => `
<div style="background:var(--wtp-panel2);padding:18px;border-radius:8px;border:1px solid var(--wtp-border)">
<div style="font-size:18px;font-weight:800;margin-bottom:4px;color:var(--wtp-ac)">${m.id}</div>
<div style="font-size:11px;color:var(--wtp-muted);margin-bottom:10px">${m.type||''}</div>
<div style="font-size:13px;line-height:1.6">${m.role||''}</div>
${m.cpu ? `<div style="margin-top:8px;font-size:11px;color:var(--wtp-muted)">CPU: ${m.cpu} · RAM: ${m.ram||'?'} · Disk: ${m.disk||'?'}</div>` : ''}
</div>`).join('');
const docker = (i.docker||[]).slice(0, 25).map(d => `
<div style="display:flex;justify-content:space-between;padding:8px 12px;background:var(--wtp-panel);border-radius:6px;margin-bottom:4px;font-size:12px">
<span style="font-weight:600">${d.name||'?'}</span>
<span style="color:var(--wtp-muted);font-family:monospace;font-size:10px">${d.status||''}</span>
</div>`).join('');
const gpus = (i.gpus||[]).map(g => `
<div style="background:var(--wtp-panel);padding:14px;border-radius:8px;margin-bottom:6px">
<div style="font-weight:700">${g.name||'?'}</div>
<div style="font-size:11px;color:var(--wtp-muted);margin-top:4px">${g.note||''}</div>
${g.mem_total_mb ? `<div style="margin-top:6px;font-family:monospace;font-size:11px">Mem: ${g.mem_used_mb}/${g.mem_total_mb} MB · Temp: ${g.temp_c}°C · Util: ${g.util_pct}%</div>` : ''}
</div>`).join('');
const subdoms = Object.entries(i.subdomains||{}).map(([d, role]) => `
<a href="https://${d}/" target="_blank" style="display:flex;justify-content:space-between;padding:8px 12px;background:var(--wtp-panel);border-radius:6px;margin-bottom:4px;font-size:12px;text-decoration:none;color:var(--wtp-text)">
<span style="font-family:monospace">${d}</span>
<span style="color:var(--wtp-muted)">${role}</span>
</a>`).join('');
main.innerHTML = `
<div style="padding:24px">
<h1 style="font-size:28px;margin-bottom:6px">🖥 Infrastructure</h1>
<div style="color:var(--wtp-muted);margin-bottom:24px">Machines · Docker · GPUs · Blade · Subdomains · tout en un</div>
<h2 style="font-size:18px;margin-bottom:12px">💻 Machines · ${(i.machines||[]).length}</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;margin-bottom:28px">${machines}</div>
<h2 style="font-size:18px;margin-bottom:12px">🔧 Blade yacineutt (Sentinel · Chrome persist)</h2>
<div style="background:var(--wtp-panel2);padding:18px;border-radius:8px;border:1px solid var(--wtp-border);margin-bottom:28px">
<div style="font-size:14px;font-weight:700;margin-bottom:8px">${i.blade?.host||'Razer Blade'}</div>
<div style="color:var(--wtp-muted);font-size:12px;margin-bottom:10px">${i.blade?.role||''}</div>
<div style="font-size:12px;margin-bottom:6px">Profile: ${i.blade?.yacineutt_profile||'?'}</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:10px">
${(i.blade?.services||[]).map(s=>`<span style="background:var(--wtp-panel);padding:4px 10px;border-radius:99px;font-size:11px;font-family:monospace">${s}</span>`).join('')}
</div>
</div>
<h2 style="font-size:18px;margin-bottom:12px">🎮 GPUs · ${(i.gpus||[]).length}</h2>
${gpus}
<div style="height:24px"></div>
<h2 style="font-size:18px;margin-bottom:12px">🐳 Docker · ${(i.docker||[]).length} containers</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:6px;margin-bottom:28px">${docker}</div>
<h2 style="font-size:18px;margin-bottom:12px">🌐 Subdomains · ${Object.keys(i.subdomains||{}).length}</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:6px">${subdoms}</div>
</div>`;
}
function renderAllPages(){
const main = document.getElementById('wtp-main');
const ap = TREE.all_pages || {};
const cats = ap.pages_by_category || {};
const catHtml = Object.entries(cats).sort((a,b)=>b[1].length-a[1].length).map(([cat, pages]) => {
const items = pages.slice(0, 50).map(p => `
<a href="${p.url}" style="display:flex;justify-content:space-between;padding:7px 11px;background:var(--wtp-panel);border:1px solid var(--wtp-border);border-radius:5px;text-decoration:none;color:var(--wtp-text);font-size:12px;transition:.15s"
onmouseover="this.style.borderColor='var(--wtp-ac)'" onmouseout="this.style.borderColor='var(--wtp-border)'">
<span style="font-weight:500">${p.name.replace(/\.html$/,'').replace(/[-_]/g,' ')}</span>
<span style="color:var(--wtp-muted);font-family:monospace;font-size:10px">${p.size_kb}k${p.is_orphan?' ·⚠':''}</span>
</a>`).join('');
return `<div style="margin-bottom:24px">
<h3 style="font-size:15px;margin-bottom:10px;display:flex;justify-content:space-between">
<span>${cat}</span><span style="color:var(--wtp-muted);font-size:11px;font-weight:400">${pages.length} pages</span>
</h3>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:4px">${items}</div>
${pages.length>50?`<div style="color:var(--wtp-muted);font-size:11px;margin-top:6px">+${pages.length-50} autres</div>`:''}
</div>`;
}).join('');
main.innerHTML = `
<div style="padding:24px">
<h1 style="font-size:28px;margin-bottom:6px">📂 Toutes les pages · ${ap.total}</h1>
<div style="color:var(--wtp-muted);margin-bottom:20px">${ap.orphans} orphelines identifiées · toutes accessibles depuis WTP</div>
<div style="margin-bottom:18px">
<input type="text" id="pg-search" placeholder="Recherche page..." oninput="_pgSearch(this.value)" style="width:100%;padding:12px 16px;background:var(--wtp-panel);border:1px solid var(--wtp-border);color:var(--wtp-text);border-radius:6px;font-size:13px;outline:none">
</div>
<div id="pg-content">${catHtml}</div>
</div>`;
}
window._pgSearch = function(q){
q = (q||'').toLowerCase();
document.querySelectorAll('#pg-content a').forEach(a => {
a.style.display = (!q || a.textContent.toLowerCase().includes(q)) ? '' : 'none';
});
};
function renderAllApis(){
const main = document.getElementById('wtp-main');
const a = TREE.apis || {};
main.innerHTML = `
<div style="padding:24px">
<h1 style="font-size:28px;margin-bottom:6px">🔌 Toutes les APIs · ${a.total}</h1>
<div style="color:var(--wtp-muted);margin-bottom:24px">Endpoints PHP dans /api/</div>
<h2 style="font-size:16px;margin-bottom:10px">Top WEVIA APIs (${(a.top_wevia||[]).length})</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:6px">
${(a.top_wevia||[]).map(api => `
<a href="/api/${api}" target="_blank" style="padding:8px 12px;background:var(--wtp-panel);border:1px solid var(--wtp-border);border-radius:5px;text-decoration:none;color:var(--wtp-text);font-size:12px;font-family:monospace">
${api}
</a>`).join('')}
</div>
</div>`;
}
function renderMultiagent(){
const main = document.getElementById('wtp-main');
const modes = TREE.multiagent_modes || [];
main.innerHTML = `
<div style="padding:24px">
<h1 style="font-size:28px;margin-bottom:6px">🤖 Multi-Agent Modes</h1>
<div style="color:var(--wtp-muted);margin-bottom:24px">3 modes coexistent via WEVIA Master chat · /wevia-master.html</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:14px">
${modes.map((m, i) => `
<div style="background:var(--wtp-panel2);padding:20px;border-radius:10px;border:1px solid var(--wtp-border);border-left:4px solid ${['#6366f1','#22c55e','#f59e0b'][i]||'#6366f1'}">
<div style="font-size:17px;font-weight:800;margin-bottom:6px">${m.mode}</div>
<div style="font-size:22px;font-weight:700;margin:8px 0">${m.agents || m.agents_target} agents</div>
<div style="font-size:12px;color:var(--wtp-muted);margin-bottom:8px">Latency: <span style="color:var(--wtp-ac);font-family:monospace">${m.latency_ms}ms</span></div>
<div style="font-size:12px;margin-bottom:10px">${m.use}</div>
<div style="font-size:10px;color:var(--wtp-muted);font-family:monospace;padding:6px 10px;background:var(--wtp-panel);border-radius:4px">${m.trigger}</div>
</div>`).join('')}
</div>
<div style="margin-top:24px;padding:18px;background:var(--wtp-panel2);border-radius:8px">
<strong>💬 Tester en live:</strong> ouvre <a href="/wevia-master.html" style="color:var(--wtp-ac)">/wevia-master.html</a> et tape "agis en multiagent"
</div>
</div>`;
}
function renderTruth(){
const main = document.getElementById('wtp-main');
const t = TREE.truth_registry || {};
main.innerHTML = `
<div style="padding:24px">
<h1 style="font-size:28px;margin-bottom:6px">🔒 Truth Registry · source unique</h1>
<div style="color:var(--wtp-muted);margin-bottom:24px">Built: <span style="font-family:monospace">${t.built_at||'?'}</span> · rebuild cron /30min</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:24px">
${[['Agents',t.agents,'#d4af37'],['Intents',t.intents,'#22c55e'],['Skills',t.skills,'#6366f1'],['Brains',t.brains,'#a855f7'],['Doctrines',t.doctrines,'#ef4444'],['Dashboards',t.dashboards,'#22d3ee'],['Providers',t.providers,'#f59e0b'],['Qdrant Cols',t.qdrant_cols,'#ec4899'],['Qdrant Points',t.qdrant_points,'#10b981'],['Autonomy',(t.autonomy_score||0)+'/100 '+(t.autonomy_level||''),'#d4af37']].map(([l,v,c])=>`
<div style="background:var(--wtp-panel2);padding:16px;border-radius:8px;border-top:2px solid ${c}">
<div style="font-size:10px;color:var(--wtp-muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">${l}</div>
<div style="font-size:24px;font-weight:800;color:${c}">${typeof v==='number' ? v.toLocaleString('fr-FR') : v}</div>
</div>`).join('')}
</div>
<div style="display:flex;gap:10px;flex-wrap:wrap">
<a href="/api/wevia-truth-registry.json" target="_blank" style="padding:10px 16px;background:var(--wtp-panel);border:1px solid var(--wtp-border);border-radius:6px;text-decoration:none;color:var(--wtp-text);font-size:13px">📄 Truth Registry JSON (560 KB)</a>
<a href="/api/source-of-truth.json" target="_blank" style="padding:10px 16px;background:var(--wtp-panel);border:1px solid var(--wtp-border);border-radius:6px;text-decoration:none;color:var(--wtp-text);font-size:13px">📋 Source of Truth (snapshot)</a>
<a href="/api/wevia.php?q=status&format=text" target="_blank" style="padding:10px 16px;background:var(--wtp-panel);border:1px solid var(--wtp-border);border-radius:6px;text-decoration:none;color:var(--wtp-text);font-size:13px">🔍 NL Query API</a>
</div>
</div>`;
}
function renderHome(){
if (!TREE) return;
const kpis = TREE.kpis || {};
const html = `
<div class="wtp-header">
<div>
<h1><div class="module-icon">🏠</div>WEVAL Technology Platform</h1>
<div class="wtp-header-desc">Portail unifié tous serveurs · tous modules · toutes capacités · all-in-one ERP</div>
</div>
<a href="/business-kpi-dashboard.php" target="_blank" class="wtp-refresh" style="background:linear-gradient(135deg,#48bb78,#b794f6);color:#fff;padding:6px 14px;border-radius:6px;text-decoration:none;font-weight:600;font-size:12px;margin-right:8px;display:inline-block">💼 V83 Business KPI (SaaS)</a><a href="/products-kpi-dashboard.php" target="_blank" class="wtp-refresh" style="background:linear-gradient(135deg,#6c9ef8,#8b5cf6);color:#fff;padding:6px 14px;border-radius:6px;text-decoration:none;font-weight:600;font-size:12px;margin-right:8px;display:inline-block">🎯 V80 Products KPI Drill-down</a>
<button class="wtp-refresh" onclick="loadTree()">↻ Refresh</button>
</div>
<!-- VISUAL-MGMT-PREMIUM-V1 doctrine 60 + L6S TOC -->
<div class="vm-grid" id="vm-dashboard">
<!-- Row 1: 4 Gauges (coverage, ethica, agents, sovereign) -->
<div class="vm-card vm-col-3">
<div class="vm-card-head"><div class="vm-card-title">🎯 Coverage Acquis</div><div class="vm-live">live</div></div>
<div class="vm-gauge" id="vm-gauge-cov"></div>
<div class="vm-gauge-label">98.29% écosystème capitalisé</div>
</div>
<div class="vm-card vm-col-3">
<div class="vm-card-head"><div class="vm-card-title">🧬 HCPs Maghreb</div><div class="vm-live">live</div></div>
<div class="vm-gauge" id="vm-gauge-ethica"></div>
<div class="vm-gauge-label">DZ · MA · TN · INTL</div>
</div>
<div class="vm-card vm-col-3">
<div class="vm-card-head"><div class="vm-card-title">🤖 Agents Fleet</div><div class="vm-card-badge" id="vm-agents-b">active</div></div>
<div class="vm-gauge" id="vm-gauge-agents"></div>
<div class="vm-gauge-label">sur ${fmt(kpis.skills_total)} skills indexed</div>
</div>
<div class="vm-card vm-col-3">
<div class="vm-card-head"><div class="vm-card-title">⚡ Sovereign IA</div><div class="vm-card-badge" id="vm-sov-b">0€</div></div>
<div class="vm-gauge" id="vm-gauge-sovereign"></div>
<div class="vm-gauge-label">cascade LLM providers</div>
</div>
<!-- Row 2: Andon L6S + L99 + NonReg + DPMO donut + flow -->
<div class="vm-card vm-col-3">
<div class="vm-card-head"><div class="vm-card-title">🚦 Andon Lean 6 Sigma</div><div class="vm-card-badge" id="vm-andon-b">ON TARGET</div></div>
<div class="vm-andon" id="vm-andon"></div>
</div>
<div class="vm-card vm-col-3">
<div class="vm-card-head"><div class="vm-card-title">🧪 NonReg · L99 · L6S</div><div class="vm-live">live</div></div>
<div class="vm-score">
<div class="vm-score-big ok" id="vm-nonreg-score">—</div>
<div class="vm-score-sub" id="vm-nonreg-sub">tests passés · dpmo 0 · 18 cycles stable</div>
</div>
<div class="vm-bars" id="vm-nonreg-bars"></div>
</div>
<div class="vm-card vm-col-3">
<div class="vm-card-head"><div class="vm-card-title">📊 DPMO (défauts/million)</div><div class="vm-card-badge">6σ target</div></div>
<div class="vm-donut-wrap">
<div class="vm-donut" id="vm-dpmo-donut"></div>
<div class="vm-donut-list" id="vm-dpmo-list"></div>
</div>
</div>
<div class="vm-card vm-col-3">
<div class="vm-card-head"><div class="vm-card-title">🔄 Value Stream Flow</div><div class="vm-card-badge">DMAIC</div></div>
<div class="vm-flow" id="vm-flow">
<div class="vm-flow-step done"><span class="num">D</span>Define</div>
<div class="vm-flow-step done"><span class="num">M</span>Measure</div>
<div class="vm-flow-step done"><span class="num">A</span>Analyze</div>
<div class="vm-flow-step work"><span class="num">I</span>Improve</div>
<div class="vm-flow-step"><span class="num">C</span>Control</div>
<div class="vm-flow-step"><span class="num">∞</span>Kaizen</div>
</div>
<div class="vm-bars" style="margin-top:12px" id="vm-toc-bars"></div>
</div>
<!-- Row 3: Heatmap écosystème + ACQUIS vs DORMANTS bars + sparkline -->
<div class="vm-card vm-col-6">
<div class="vm-card-head"><div class="vm-card-title">🔥 Heatmap écosystème — santé 144 composants</div><div class="vm-live">real-time</div></div>
<div class="vm-heat" id="vm-heat"></div>
<div class="vm-heat-legend">
<span style="background:#1f2436"></span>idle
<span style="background:rgba(16,185,129,.35)"></span>ok
<span style="background:rgba(16,185,129,1)"></span>hot
<span style="background:rgba(245,158,11,.6)"></span>warn
<span style="background:rgba(239,68,68,.8)"></span>fail
</div>
</div>
<div class="vm-card vm-col-6">
<div class="vm-card-head"><div class="vm-card-title">📈 ACQUIS LIVE vs À WIRER (dormants)</div><div class="vm-card-badge" id="vm-acq-b">—</div></div>
<div class="vm-bars" id="vm-acq-bars"></div>
<svg class="vm-spark" viewBox="0 0 400 54" preserveAspectRatio="none" style="margin-top:10px">
<defs>
<linearGradient id="grad-spark" x1="0" x2="1"><stop offset="0%" stop-color="#22d3ee"/><stop offset="100%" stop-color="#a855f7"/></linearGradient>
<linearGradient id="grad-spark-area" x1="0" x2="0" y1="0" y2="1"><stop offset="0%" stop-color="#a855f7" stop-opacity=".5"/><stop offset="100%" stop-color="#22d3ee" stop-opacity="0"/></linearGradient>
</defs>
<path class="area" id="vm-spark-area" d=""/>
<path class="line" id="vm-spark-line" d=""/>
</svg>
</div>
</div>
<!-- Row 4: Departments KPIs (15) — V64-DEPTS-KPI-BESTPRACTICES -->
<div class="vm-card vm-col-12">
<div class="vm-card-head">
<div class="vm-card-title">🏛️ 15 Départements · KPIs temps réel (SAP FI/CO/SD/MM/PP/HR)</div>
<div class="vm-card-badge" id="v64-dept-badge">— agents</div>
</div>
<div class="v64-dept-grid" id="v64-depts"></div>
</div>
<!-- Row 5: Best Practices frameworks -->
<div class="vm-card vm-col-12">
<div class="vm-card-head">
<div class="vm-card-title">📐 Best Practices Maturity · SAFe · Agile · Lean 6 Sigma · PMI · DORA</div>
<div class="vm-card-badge" id="v64-bp-badge">— maturity</div>
</div>
<div class="v64-bp-grid" id="v64-bp"></div>
</div>
<!-- Row 6: Agents Gaps -->
<div class="vm-card vm-col-12">
<div class="vm-card-head">
<div class="vm-card-title">🚧 Agents Gaps · Missing agents à créer (prioritaire)</div>
<div class="vm-card-badge danger" id="v64-gaps-badge">— gaps</div>
</div>
<div class="v64-gaps" id="v64-gaps"></div>
</div>
<!-- Row 7: V65 ERP Gap-Fill OFFRE COMMERCIALE -->
<div class="vm-card vm-col-12" style="background:linear-gradient(135deg,rgba(20,184,166,0.10),rgba(168,85,247,0.08));border:1px solid rgba(20,184,166,0.25);position:relative;overflow:hidden">
<div class="vm-card-head">
<div class="vm-card-title">💼 Offre commerciale · ERP Gap-Fill Agents <span style="color:var(--text-3);font-weight:400;margin-left:6px">— vendable à nos clients Retail/Pharma/Banque/Industrie/Services/Conseil/Énergie</span></div>
<div class="vm-card-badge">V65 · 7.3M€ TAM</div>
</div>
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:14px">
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:22px;font-weight:800;color:#fca5a5">25</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Risques 5×5</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:22px;font-weight:800;color:#fbbf24">33</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">ERP Gaps</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:22px;font-weight:800;color:#a5b4fc">7</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Verticaux</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:22px;font-weight:800;color:#5eead4">149</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Agents pack</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:22px;font-weight:800;background:linear-gradient(135deg,#22d3ee,#a855f7);-webkit-background-clip:text;background-clip:text;color:transparent">7.3M€</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">TAM pipeline</div></div>
</div>
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
<a href="/erp-gap-fill-offer.html" style="padding:9px 18px;background:linear-gradient(135deg,#6366f1,#a855f7);color:white;border-radius:8px;text-decoration:none;font-size:12.5px;font-weight:600">📑 Ouvrir offre complète</a>
<span style="font-size:11.5px;color:var(--text-2)">Services : Discovery 5K€ · POC 15-25K€ · Rollout 80-300K€ · Managed 30-80K€/an</span>
</div>
</div>
<!-- /V65-ERP-GAPFILL-OFFER -->
<!-- Row 8: V66 Pain Points Atlas All ERPs -->
<div class="vm-card vm-col-12" style="background:linear-gradient(135deg,rgba(234,179,8,0.08),rgba(168,85,247,0.06));border:1px solid rgba(234,179,8,0.3);position:relative;overflow:hidden">
<div class="vm-card-head">
<div class="vm-card-title">🗺️ Pain Points Atlas · All ERPs <span style="color:var(--text-3);font-weight:400;margin-left:6px">— SAP/Oracle/Sage/Odoo/D365/NetSuite/Workday/Salesforce/Infor/IFS/Epicor/Veeva/Temenos (25 ERPs)</span></div>
<div class="vm-card-badge" style="background:linear-gradient(135deg,#eab308,#f59e0b);color:#0b0d15">V66 · 17.36M€ savings/client</div>
</div>
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:14px">
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:22px;font-weight:800;color:#a5b4fc">25</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">ERPs couverts</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:22px;font-weight:800;color:#fca5a5">35</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Pain points</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:22px;font-weight:800;color:#5eead4">35</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Agents uniques</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:22px;font-weight:800;color:#fbbf24">496k€</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Avg savings/agent/an</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:22px;font-weight:800;background:linear-gradient(135deg,#eab308,#f59e0b);-webkit-background-clip:text;background-clip:text;color:transparent">17.36M€</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Max savings/client/an</div></div>
</div>
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
<a href="/pain-points-atlas.html" style="padding:9px 18px;background:linear-gradient(135deg,#eab308,#f59e0b);color:#0b0d15;border-radius:8px;text-decoration:none;font-size:12.5px;font-weight:700">🗺️ Ouvrir Pain Points Atlas</a>
<a href="/infra-tour-2s-5c-blade.html" style="padding:9px 18px;background:linear-gradient(135deg,#06b6d4,#8b5cf6);color:#0b0d15;border-radius:8px;text-decoration:none;font-size:12.5px;font-weight:700">🗺️ Ouvrir Infra Tour 2S+5C+Blade</a>
<a href="/growth-engine-v2.html" style="padding:9px 18px;background:linear-gradient(135deg,#eab308,#f59e0b);color:#0b0d15;border-radius:8px;text-decoration:none;font-size:12.5px;font-weight:700">🗺️ Ouvrir Growth Engine v4</a>
<a href="/agent-roi-simulator.html" style="padding:9px 18px;background:linear-gradient(135deg,#14b8a6,#6366f1);color:white;border-radius:8px;text-decoration:none;font-size:12.5px;font-weight:700">🧮 ROI Simulator (V67)</a>
<a href="/erp-gap-fill-offer.html" style="padding:9px 18px;background:var(--bg-3);color:var(--text);border:1px solid var(--border);border-radius:8px;text-decoration:none;font-size:12.5px;font-weight:500">📑 Offre V65</a>
<span style="font-size:11.5px;color:var(--text-2)">🐕 Dogfood : WEVAL comble 35 gaps internes = 2.4M€ savings/an (preuve par l'exemple)</span>
</div>
</div>
<!-- /V66-ALL-ERPS-PAINPOINTS -->
<!-- Row 9: V69 DG Command Center -->
<div class="vm-card vm-col-12" style="background:linear-gradient(135deg,rgba(239,68,68,0.06),rgba(168,85,247,0.06));border:1px solid rgba(239,68,68,0.25);position:relative;overflow:hidden">
<div class="vm-card-head">
<div class="vm-card-title">🎖️ DG Command Center · Real-time <span style="color:var(--text-3);font-weight:400;margin-left:6px">— TOC · Conversion · Data · Marketing · CRM · Risk · Alertes</span></div>
<div class="vm-card-badge danger">V69 · 3 alertes critical</div>
</div>
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:14px">
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#fca5a5" id="dg-alerts">—</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Alertes DG</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#fbbf24" id="dg-toc">—</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">TOC Bottleneck</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#5eead4" id="dg-pipe">—</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Pipeline k€</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#a5b4fc" id="dg-opps">—</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Opps actives</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#fca5a5" id="dg-risks">—</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Risques critical</div></div>
</div>
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
<a href="/dg-command-center.html" style="padding:9px 18px;background:linear-gradient(135deg,#ef4444,#a855f7);color:white;border-radius:8px;text-decoration:none;font-size:12.5px;font-weight:700">🎖️ Ouvrir DG Command Center</a>
<span style="font-size:11.5px;color:var(--text-2)">⏱ auto-refresh 20s · TOC Goldratt + Conversion funnel + CRM stages + 12 risques</span>
</div>
</div>
<!-- Row 10: V71 Intelligence & Growth Insights -->
<div class="vm-card vm-col-12" style="background:linear-gradient(135deg,rgba(99,102,241,0.08),rgba(234,179,8,0.06));border:1px solid rgba(99,102,241,0.28);position:relative">
<div class="vm-card-head">
<div class="vm-card-title">🌐 Intelligence &amp; Growth Insights (V71) <span style="color:var(--text-3);font-weight:400;margin-left:6px">— DarkScout + WEVL Predict + Agility + Leads + Opportunities</span></div>
<div class="vm-card-badge info">V71 · 8 comp · 13 innov · 36 bots</div>
</div>
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:14px">
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#fca5a5" id="v71-comp">8</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Compétiteurs</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#86efac" id="v71-innov">13</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Innovations</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#d4a7fa" id="v71-agil">12</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Agility Gap</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#7dd3fc" id="v71-chat">40/40</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Chatbots</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#fde047" id="v71-oppo">1.8M€</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Opportunities</div></div>
</div>
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
<a href="/intelligence-growth.html" style="padding:9px 18px;background:linear-gradient(135deg,#6366f1,#eab308);color:white;border-radius:8px;text-decoration:none;font-size:12.5px;font-weight:700">🌐 Ouvrir Intelligence &amp; Growth</a>
<span style="font-size:11.5px;color:var(--text-2)">⏱ auto-refresh 30s · veille compétiteurs + innovations + leads + opportunités LinkedIn/Web</span>
</div>
</div>
<!-- /V71-INTELLIGENCE-GROWTH -->
<!-- /V69-DG-COMMAND-CENTER -->
<!-- Row 10: V70 Enterprise Complete + Integration Hub -->
<div class="vm-card vm-col-12" style="background:linear-gradient(135deg,rgba(234,179,8,0.08),rgba(6,182,212,0.06));border:1px solid rgba(234,179,8,0.3);position:relative;overflow:hidden">
<div class="vm-card-head">
<div class="vm-card-title">🏢 Enterprise Complete · All Depts · All KPIs · All Integrations <span style="color:var(--text-3);font-weight:400;margin-left:6px">— 20 départements · 169 KPIs · 61 connecteurs universels</span></div>
<div class="vm-card-badge" style="background:linear-gradient(135deg,#eab308,#f59e0b);color:#0b0d15">V70 · Universal</div>
</div>
<div style="display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-bottom:14px">
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#5eead4">20</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Départements</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#fbbf24">169</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">KPIs Total</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#a5b4fc">61</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Intégrations</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#86efac">16</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">ERP supportés</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#7dd3fc">9</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Auth methods</div></div>
<div style="text-align:center;padding:10px;background:var(--bg-3);border-radius:8px"><div style="font-size:20px;font-weight:800;color:#fca5a5">10</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Protocoles</div></div>
</div>
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
<a href="/enterprise-complete.html" style="padding:9px 18px;background:linear-gradient(135deg,#eab308,#06b6d4);color:#0b0d15;border-radius:8px;text-decoration:none;font-size:12.5px;font-weight:700">🏢 Ouvrir Enterprise Complete</a>
<span style="font-size:11.5px;color:var(--text-2)">🔌 Universal adapter: SAP · Oracle · Sage · Salesforce · Workday · Stripe · PostgreSQL · MongoDB · + scraping fallback</span>
</div>
</div>
<!-- /V70-ENTERPRISE-COMPLETE -->
<!-- Row 11: V85 Business KPI SaaS Premium -->
<div class="vm-card vm-col-12" style="background:linear-gradient(135deg,rgba(72,187,120,0.10),rgba(183,148,246,0.08),rgba(108,158,248,0.05));border:1px solid rgba(183,148,246,0.35);position:relative;overflow:hidden">
<div class="vm-card-head">
<div class="vm-card-title">💼 V85 Business KPI · SaaS Ready <span style="color:var(--text-3);font-weight:400;margin-left:6px">— Revenue · Customer · Growth · Predictive · SLA · Productivité</span></div>
<div class="vm-card-badge" style="background:linear-gradient(135deg,#48bb78,#b794f6,#6c9ef8);color:#fff">V85 · Premium</div>
</div>
<div style="display:grid;grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:16px">
<div style="text-align:center;padding:12px;background:var(--bg-3);border-radius:10px;border-left:3px solid #48bb78"><div style="font-size:22px;font-weight:800;color:#48bb78" id="v85-total-kpis">—</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Total KPIs</div></div>
<div style="text-align:center;padding:12px;background:var(--bg-3);border-radius:10px;border-left:3px solid #6c9ef8"><div style="font-size:22px;font-weight:800;color:#6c9ef8" id="v85-categories">—</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Categories</div></div>
<div style="text-align:center;padding:12px;background:var(--bg-3);border-radius:10px;border-left:3px solid #48bb78"><div style="font-size:22px;font-weight:800;color:#48bb78" id="v85-live">—</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Live on target</div></div>
<div style="text-align:center;padding:12px;background:var(--bg-3);border-radius:10px;border-left:3px solid #f6ad55"><div style="font-size:22px;font-weight:800;color:#f6ad55" id="v85-warn">—</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Below target</div></div>
<div style="text-align:center;padding:12px;background:var(--bg-3);border-radius:10px;border-left:3px solid #b794f6"><div style="font-size:22px;font-weight:800;color:#b794f6" id="v85-wire">—</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Wire needed</div></div>
<div style="text-align:center;padding:12px;background:var(--bg-3);border-radius:10px;border-left:3px solid #6c9ef8"><div style="font-size:22px;font-weight:800;color:#6c9ef8" id="v85-completeness">—</div><div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-top:3px">Data Completeness</div></div>
</div>
<div id="v85-categories-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;margin-bottom:16px">
<div style="padding:16px;background:var(--bg-3);border-radius:10px;color:var(--text-3);text-align:center;grid-column:1/-1">Loading V85 business KPIs (7 categories × 8 KPIs = 56)...</div>
</div>
<div style="margin-bottom:14px">
<div style="font-size:12px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px">⚡ KPI Highlights (live data)</div>
<div id="v85-highlights" style="display:grid;grid-template-columns:repeat(5,1fr);gap:10px">
<div style="padding:12px;background:var(--bg-3);border-radius:8px;color:var(--text-3);grid-column:1/-1">Loading highlights...</div>
</div>
</div>
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
<a href="/business-kpi-dashboard.php" target="_blank" style="padding:9px 18px;background:linear-gradient(135deg,#48bb78,#b794f6);color:#fff;border-radius:8px;text-decoration:none;font-size:12.5px;font-weight:700">💼 Business KPI Dashboard (Drill-down)</a>
<a href="/products-kpi-dashboard.php" target="_blank" style="padding:9px 18px;background:linear-gradient(135deg,#6c9ef8,#8b5cf6);color:#fff;border-radius:8px;text-decoration:none;font-size:12.5px;font-weight:700">🎯 V80 Products KPI Drill-down</a>
<span style="font-size:11.5px;color:var(--text-2)">🚀 SaaS-ready: Revenue · Customer · Growth · Predictive · SLA · Productivité</span>
</div>
</div>
<!-- /V85-BUSINESS-KPI-SAAS-PREMIUM -->
<!-- Row 12: V67-WEVIA-APPLE — Photos Scanner OSS Intelligence (Opus 19avr) -->
<section id="wevia-apple-section" style="margin:16px 12px;padding:20px 24px;border-radius:16px;background:linear-gradient(135deg,rgba(0,0,0,.35),rgba(30,30,50,.25));backdrop-filter:blur(10px);border:1px solid rgba(229,229,234,.18);box-shadow:0 6px 24px rgba(0,0,0,.28)">
<div style="display:flex;align-items:center;gap:14px;margin-bottom:12px">
<div style="width:44px;height:44px;background:linear-gradient(135deg,#000,#1d1d1f);border-radius:11px;display:grid;place-items:center;font-size:26px">🍎</div>
<div style="flex:1">
<div style="font-size:1.05rem;font-weight:700;letter-spacing:-.2px">WEVIA Apple — Photos Scanner OSS Intelligence</div>
<div style="font-size:.78rem;color:rgba(229,229,234,.7);margin-top:2px">Upload photo iPhone → OCR tesseract + Vision Gemini 2.5 → extract GitHub/OSS/Docker · iPhone Shortcuts ready</div>
</div>
<a href="/wevia-apple.html" style="padding:8px 14px;background:rgba(96,165,250,.15);border:1px solid rgba(96,165,250,.4);color:#60a5fa;border-radius:8px;text-decoration:none;font-size:.85rem;font-weight:600;white-space:nowrap">Ouvrir →</a>
</div>
<div id="wevia-apple-kpi" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:8px;margin-top:8px">
<div style="padding:10px 12px;background:rgba(96,165,250,.08);border:1px solid rgba(96,165,250,.25);border-radius:8px"><div style="font-size:.68rem;color:rgba(229,229,234,.6);text-transform:uppercase;letter-spacing:.6px">Photos</div><div id="wa-k-total" style="font-size:1.4rem;font-weight:700">—</div></div>
<div style="padding:10px 12px;background:rgba(251,191,36,.08);border:1px solid rgba(251,191,36,.25);border-radius:8px"><div style="font-size:.68rem;color:rgba(229,229,234,.6);text-transform:uppercase;letter-spacing:.6px">OSS trouvés</div><div id="wa-k-oss" style="font-size:1.4rem;font-weight:700;color:#fbbf24">—</div></div>
<div style="padding:10px 12px;background:rgba(34,197,94,.08);border:1px solid rgba(34,197,94,.25);border-radius:8px"><div style="font-size:.68rem;color:rgba(229,229,234,.6);text-transform:uppercase;letter-spacing:.6px">GitHub URLs</div><div id="wa-k-gh" style="font-size:1.4rem;font-weight:700;color:#22c55e">—</div></div>
<div style="padding:10px 12px;background:rgba(192,132,252,.08);border:1px solid rgba(192,132,252,.25);border-radius:8px"><div style="font-size:.68rem;color:rgba(229,229,234,.6);text-transform:uppercase;letter-spacing:.6px">Top</div><div id="wa-k-top" style="font-size:.95rem;font-weight:600;line-height:1.3;color:#c084fc">—</div></div>
</div>
<script>(function(){fetch('/api/wevia-apple-scan.php?action=stats').then(r=>r.json()).then(d=>{document.getElementById('wa-k-total').textContent=d.scans_total||0;document.getElementById('wa-k-oss').textContent=d.oss_total||0;document.getElementById('wa-k-gh').textContent=d.github_urls_total||0;var top=d.top_projects?Object.entries(d.top_projects)[0]:null;document.getElementById('wa-k-top').textContent=top?(top[0]+' ('+top[1]+')'):'—';}).catch(()=>{});})();<\/script>
</section>
<!-- /V67-WEVIA-APPLE -->
<!-- /VISUAL-MGMT-PREMIUM-V1 -->
<div class="wtp-section-title">15 modules ERP disponibles</div>
<div class="wtp-home-grid">
${Object.entries(TREE.modules).map(([id, mod]) => `
<div class="wtp-home-module" style="--mod-color:${mod.color}" onclick="navigateTo('${id}')">
<div class="wtp-home-icon">${mod.icon}</div>
<div class="wtp-home-label">${mod.label}</div>
<div class="wtp-home-tag">${mod.tagline}</div>
<div class="wtp-home-stat">
<div><span class="num">${(mod.submodules||[]).length}</span><span class="lbl">sub-modules</span></div>
<div><span class="num">${countAssets(mod)}</span><span class="lbl">assets</span></div>
</div>
</div>`).join('')}
</div>
<div class="wtp-section-title">Subdomains live</div>
<div class="wtp-kpis">
${Object.entries(TREE.subdomains||{}).map(([dom, name]) => `
<a href="https://${dom}/" target="_blank" class="wtp-kpi" style="text-decoration:none;display:block">
<div class="wtp-kpi-label">${name}</div>
<div style="font-size:12px;color:var(--text-1);margin-top:4px;font-family:'JetBrains Mono',monospace;word-break:break-all">${dom}</div>
</a>`).join('')}
</div>
<div class="wtp-section-title">Docker containers (${TREE.docker.length})</div>
<div class="wtp-tiles">
${TREE.docker.map(d => `
<div class="wtp-tile" style="--tile-color:#06b6d4">
<div class="wtp-tile-head">
<div class="wtp-tile-title">${d.name}</div>
<div class="wtp-tile-badge live">● running</div>
</div>
<div class="wtp-tile-desc" style="font-family:'JetBrains Mono',monospace;font-size:10.5px">${d.status}</div>
</div>`).join('')}
</div>`;
document.getElementById('wtp-main').innerHTML = html;
}
function countAssets(mod){
let n = 0;
(mod.submodules||[]).forEach(s => {
n += (s.pages||[]).length + (s.apis||[]).length + (s.scripts||[]).length;
});
return n;
}
function renderModule(modId){
const mod = TREE.modules[modId];
if (!mod) { document.getElementById('wtp-main').innerHTML = '<div class="wtp-loading">Module introuvable</div>'; return; }
const html = `
<div class="wtp-header">
<div>
<h1><div class="module-icon" style="color:${mod.color}">${mod.icon}</div>${mod.label}</h1>
<div class="wtp-header-desc">${mod.tagline}</div>
</div>
<button class="wtp-refresh" onclick="loadTree()">↻ Refresh</button>
</div>
<div class="wtp-section-title">${(mod.submodules||[]).length} sous-modules</div>
<div class="wtp-tiles">
${(mod.submodules||[]).map(sub => renderTile(sub, mod.color)).join('')}
</div>`;
document.getElementById('wtp-main').innerHTML = html;
}
function renderTile(sub, color){
const badge = sub.status === 'dormant' ? '<div class="wtp-tile-badge dormant">● dormant</div>'
: sub.status === 'live' ? '<div class="wtp-tile-badge live">● live</div>' : '';
const desc = sub.desc ? `<div class="wtp-tile-desc">${sub.desc}</div>` : '';
const pages = (sub.pages||[]).map(p => {
const label = p.replace(/^https?:\/\/[^/]+\//,'').replace(/\.html$/,'').substring(0,28);
const href = p.startsWith('http') ? p : '/' + p;
return `<a href="${href}" target="_blank" class="wtp-pill page" onclick="event.stopPropagation()">📄 ${label}</a>`;
}).join(' ');
const apis = (sub.apis||[]).map(a => {
const label = a.replace(/^https?:\/\/[^/]+/,'').substring(0,28) || a.substring(0,28);
return `<a href="${a}" target="_blank" class="wtp-pill api" onclick="event.stopPropagation()">⚡ ${label}</a>`;
}).join(' ');
const scripts = (sub.scripts||[]).slice(0,4).map(s => `<span class="wtp-pill script">🔧 ${s}</span>`).join(' ');
const moreScripts = (sub.scripts||[]).length > 4 ? `<span class="wtp-pill script">+${sub.scripts.length-4} scripts</span>` : '';
const docker = sub.docker ? `<span class="wtp-pill docker">🐳 ${sub.docker}</span>` : '';
const path = sub.path ? `<span class="wtp-pill path">📁 ${sub.path}</span>` : '';
const url = sub.url ? `<a href="${sub.url}" target="_blank" class="wtp-pill" onclick="event.stopPropagation()">🌐 ${sub.url.substring(0,35)}</a>` : '';
const stakeholders = (sub.stakeholders||[]).length ? `<div class="wtp-tile-meta" style="margin-top:6px"><span class="wtp-pill">👤 ${sub.stakeholders.join(', ')}</span></div>` : '';
const note = sub.note ? `<div style="font-size:10.5px;color:var(--warning);margin-top:6px;font-style:italic">⚠ ${sub.note}</div>` : '';
const firstUrl = (sub.pages||[])[0] || (sub.apis||[])[0] || sub.url || '#';
return `<div class="wtp-tile" style="--tile-color:${color}" onclick="openUrl('${firstUrl}')">
<div class="wtp-tile-head">
<div class="wtp-tile-title">${sub.label}</div>
${badge}
</div>
${desc}
<div class="wtp-tile-meta">${pages}${apis}${scripts}${moreScripts}${docker}${path}${url}</div>
${stakeholders}
${note}
</div>`;
}
function openUrl(u){
if (u.startsWith('http')) window.open(u, '_blank');
else window.location.href = u;
}
function renderStatusBar(){
const k = TREE.kpis || {};
const setT = (id, v) => { const e=document.getElementById(id); if(e) e.textContent=v; };
setT('st-docker', (k.docker_running||''));
setT('st-prov', (k.sovereign_providers||'')+'/13');
setT('st-qdrant',(k.qdrant_collections||''));
setT('st-nr', (k.nonreg_pass||'')+'/'+(k.nonreg_total||''));
setT('st-hcp', fmt(k.ethica_hcps));
setT('st-ver', TREE.version || 'v2.0');
// Time
const updT = () => document.getElementById('st-time').textContent = new Date().toLocaleTimeString('fr-FR');
updT(); setInterval(updT, 1000);
}
function fmt(n){ if (n==null) return ''; return Number(n).toLocaleString('fr-FR'); }
// Search (fuzzy)
const searchInput = document.getElementById('wtp-search-input');
const searchResults = document.getElementById('wtp-search-results');
searchInput.addEventListener('input', debounce((e) => {
const q = e.target.value.toLowerCase().trim();
if (q.length < 2) { searchResults.classList.remove('show'); return; }
const hits = [];
Object.entries(TREE.modules).forEach(([modId, mod]) => {
(mod.submodules||[]).forEach(sub => {
const hay = [sub.label, sub.desc||'', ...(sub.pages||[]), ...(sub.apis||[]), ...(sub.scripts||[])].join(' ').toLowerCase();
if (hay.includes(q)) hits.push({modId, mod, sub});
});
});
const html = hits.slice(0, 20).map(h => `
<div class="wtp-search-result" onclick="navigateTo('${h.modId}');document.getElementById('wtp-search-results').classList.remove('show');document.getElementById('wtp-search-input').value=''">
<div class="title">${highlight(h.sub.label, q)}</div>
<div class="module">${h.mod.icon} ${h.mod.label}</div>
</div>`).join('');
searchResults.innerHTML = html || '<div class="wtp-search-result"><div class="title" style="color:var(--text-3)">Aucun résultat</div></div>';
searchResults.classList.add('show');
}, 200));
searchInput.addEventListener('blur', () => setTimeout(() => searchResults.classList.remove('show'), 200));
function highlight(txt, q){ return txt.replace(new RegExp('('+escapeReg(q)+')','ig'), '<mark>$1</mark>'); }
function escapeReg(s){ return s.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'); }
function debounce(fn, ms){ let t; return (...a) => { clearTimeout(t); t=setTimeout(()=>fn(...a), ms); }; }
function showNotifications(){ alert('Notifications: NonReg 153/153 OK · Guardian watchdog active · 0 bans CrowdSec'); }
loadTree();
setInterval(loadTree, 60000); // refresh every 60s
// ===== VISUAL-MGMT-PREMIUM-V1 (doctrine 60) =====
async function vmUpdate(){
if (!document.getElementById('vm-dashboard')) return;
let v63 = null;
try { const r = await fetch('/api/wevia-v63-acquired-enriched.php?action=full&t='+Date.now()); v63 = await r.json(); } catch(e){}
const s = v63 ? v63.summary : {};
const l6s = v63 ? v63.lean6sigma : {};
const k = (TREE && TREE.kpis) || {};
// Gauges — Coverage
gaugeSVG('vm-gauge-cov', s.coverage_ratio_pct||0, '%', '#14b8a6', '#a855f7');
// Ethica
const ethicaPct = k.ethica_hcps ? Math.min(100, (k.ethica_hcps/150000*100)) : 0;
gaugeSVG('vm-gauge-ethica', ethicaPct, 'K', '#f59e0b', '#ef4444', k.ethica_hcps ? (k.ethica_hcps/1000).toFixed(0) : '?');
// Agents
const agPct = k.agents_total && k.skills_total ? Math.min(100, (k.agents_total/k.skills_total*100)) : 0;
gaugeSVG('vm-gauge-agents', agPct, '%', '#6366f1', '#a855f7', k.agents_total||'?');
// Sovereign
gaugeSVG('vm-gauge-sovereign', (k.sovereign_providers||0)/13*100, '/13', '#22d3ee', '#a855f7', k.sovereign_providers||0);
// Andon L6S
const andon = document.getElementById('vm-andon');
const status = l6s.status || 'UNKNOWN';
const lights = [
{color:'green', on: status === 'ON TARGET', label:'GO'},
{color:'yellow', on: /WARN|CAUTION/i.test(status), label:'WARN'},
{color:'red', on: /FAIL|CRITICAL|OFF/i.test(status), label:'STOP'}
];
andon.innerHTML = lights.map(l => `<div><div class="vm-light ${l.on?'on':'off'} ${l.color}"></div><div class="vm-light-label">${l.label}</div></div>`).join('');
const andonB = document.getElementById('vm-andon-b');
andonB.textContent = status;
andonB.className = 'vm-card-badge' + (status==='ON TARGET'?'':(status.includes('WARN')?' warn':' danger'));
// NonReg score
const nrPass = l6s.pass || k.nonreg_pass || 0;
const nrTotal = l6s.pass + (l6s.fail||0) || k.nonreg_total || 0;
const nrScore = l6s.score_l99 || (nrTotal ? Math.round(nrPass/nrTotal*100) : 0);
const nrEl = document.getElementById('vm-nonreg-score');
nrEl.textContent = nrPass + '/' + nrTotal;
nrEl.className = 'vm-score-big ' + (nrScore>=95?'ok':(nrScore>=80?'warn':'danger'));
document.getElementById('vm-nonreg-sub').textContent = nrScore + '% · DPMO ' + (l6s.dpmo||0) + ' · ' + (l6s.cycles_stable_v42_v63||0) + ' cycles stable';
const nrBars = [
{lbl:'NonReg', v:nrPass, max:nrTotal, cls:'' },
{lbl:'L99', v:l6s.pass||0, max:(l6s.pass||0)+(l6s.fail||0)||1, cls:'' },
{lbl:'APIs', v:s.total_apis_active||0, max:12, cls:'' },
{lbl:'Intents', v:s.total_intents_wired||0, max:100, cls:'' }
];
document.getElementById('vm-nonreg-bars').innerHTML = nrBars.map(b => {
const pct = b.max ? Math.min(100, b.v/b.max*100) : 0;
return `<div class="vm-bar-row"><div class="vm-bar-label">${b.lbl}</div><div class="vm-bar-track"><div class="vm-bar-fill ${b.cls}" style="width:0%" data-pct="${pct}"></div></div><div class="vm-bar-count">${b.v}/${b.max}</div></div>`;
}).join('');
setTimeout(()=>{ document.querySelectorAll('#vm-nonreg-bars .vm-bar-fill').forEach(el=>{ el.style.width = el.dataset.pct+'%'; }); }, 60);
// DPMO donut
const dpmo = l6s.dpmo || 0;
const sigmaLevel = dpmo <= 3.4 ? 6 : dpmo <= 233 ? 5 : dpmo <= 6210 ? 4 : 3;
const dpmoPct = Math.min(100, Math.max(0, 100 - (dpmo/10000*100)));
document.getElementById('vm-dpmo-donut').innerHTML = donutSVG(dpmoPct, sigmaLevel+'σ', 'sigma');
document.getElementById('vm-dpmo-list').innerHTML = `
<div><span><span class="dot" style="background:#10b981"></span>On target</span><span>${dpmo <= 3.4 ? '✓' : '—'}</span></div>
<div><span><span class="dot" style="background:#f59e0b"></span>Warn (>233)</span><span>${dpmo > 3.4 && dpmo <= 233 ? '!' : '—'}</span></div>
<div><span><span class="dot" style="background:#ef4444"></span>Fail (>6210)</span><span>${dpmo > 6210 ? '✗' : '—'}</span></div>
<div style="margin-top:4px;color:var(--text-2);font-size:10px">target 3.4 DPMO</div>`;
// TOC bars (bottleneck) — intents wired vs target
const tocBars = [
{lbl:'Intents', v:s.total_intents_wired||0, max:100},
{lbl:'Skills OSS', v:s.total_skills_oss||0, max:5500},
{lbl:'Vectors', v:s.total_vectors_rag||0, max:20000},
{lbl:'Doctrines', v:s.total_doctrines||0, max:77}
];
document.getElementById('vm-toc-bars').innerHTML = tocBars.map(b => {
const pct = b.max ? Math.min(100, b.v/b.max*100) : 0;
return `<div class="vm-bar-row"><div class="vm-bar-label">${b.lbl}</div><div class="vm-bar-track"><div class="vm-bar-fill" style="width:0%" data-pct="${pct}"></div></div><div class="vm-bar-count">${fmt(b.v)}</div></div>`;
}).join('');
setTimeout(()=>{ document.querySelectorAll('#vm-toc-bars .vm-bar-fill').forEach(el=>{ el.style.width = el.dataset.pct+'%'; }); }, 60);
// V96.8 Opus 19avr HONEST heatmap — 144 REAL named components (doctrine 4 honnêteté)
// Replaces V72 pseudo-random decoration (((seed+i*37)*2654435761)%100) which showed fake red/orange
// Each cell now = named component (11 infra + 20 dashboards + 25 ERPs + 60 pain points + 10 APIs + 18 skills)
// Hover = real name + status + details · Click = open real link
const heat = document.getElementById('vm-heat');
// Loading placeholder immediately
heat.innerHTML = Array.from({length:144},(_,i)=>'<div class="vm-heat-cell" data-v="0" data-idx="'+i+'" title="Loading ecosystem health…"></div>').join('');
// Fetch real health data
fetch('/api/wevia-ecosystem-health-144.php?t='+Date.now()).then(r=>r.json()).then(eh => {
if (!eh || !eh.cells) return;
const statusToV = {fail:'r', warn:'w', hot:'4', ok:'2', idle:'0'};
const statusIcon = {fail:'❌', warn:'⚠️', hot:'🔥', ok:'✅', idle:'⏸️'};
const newCells = eh.cells.map(c => {
const v = statusToV[c.status] || '0';
const ico = statusIcon[c.status] || '';
// Escape quotes in tooltip
const tip = (ico + ' ' + c.name + ' · ' + (c.status||'').toUpperCase() + ' · ' + (c.details||'') + ' · [' + (c.category||'') + ']').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
return '<div class="vm-heat-cell" data-v="'+v+'" data-idx="'+c.idx+'" data-link="'+(c.link||'#')+'" data-status="'+c.status+'" title="'+tip+'"></div>';
}).join('');
heat.innerHTML = newCells;
// Make ALL cells clickable to their real component (doctrine 4)
heat.querySelectorAll('.vm-heat-cell').forEach(el => {
const link = el.dataset.link;
if (link && link !== '#') {
el.style.cursor = 'pointer';
el.addEventListener('click', () => window.open(link, '_blank'));
}
});
console.log('Heatmap V96.8 HONEST: 144 named components · stats:', eh.stats);
}).catch(e => console.error('[V96.8] ecosystem health fetch failed', e));
// Load actionable data for red/warn cells
Promise.all([
fetch('/api/wevia-v69-dg-command-center.php').then(r=>r.json()).catch(()=>null),
fetch('/api/wevia-v71-intelligence-growth.php').then(r=>r.json()).catch(()=>null)
]).then(([d69, d71]) => {
const actions = [];
if (d69) {
(d69.alerts_dg||[]).forEach(a => {
if (['critical','high'].includes(a.level)) actions.push({title:a.title, detail:a.detail, url:a.action_link||'/dg-command-center.html', icon:a.icon||'🚨'});
});
(d69.risks||[]).forEach(r => {
if (r.priority === 'critical') actions.push({title:r.title, detail:r.mitigation, url:'/dg-command-center.html', icon:'⚠️'});
});
/* V46 wire DG summary KPIs (doctrine #14 additif) */
if (d69.summary) {
var s69 = d69.summary;
var _setDg = function(id, v){ var el=document.getElementById(id); if(el && v!=null && v!=='') el.textContent=v; };
_setDg('dg-alerts', s69.alerts_dg_count);
_setDg('dg-toc', s69.toc_bottleneck_label);
_setDg('dg-pipe', (s69.pipeline_value_keur||0) + 'k€');
_setDg('dg-opps', s69.active_clients);
_setDg('dg-risks', s69.risks_critical);
}
}
if (d71) {
(d71.opportunities_watch?.opportunities||[]).forEach(o => {
if (['critical','high'].includes(o.urgency)) actions.push({title:o.signal, detail:o.action, url:'/sales-hub.html', icon:'🎯'});
});
}
// Make red cells clickable with real action
const redCells = document.querySelectorAll('.vm-heat-cell[data-v="r"]');
redCells.forEach((el, i) => {
const act = actions[i % Math.max(actions.length, 1)];
if (act) {
el.style.cursor = 'pointer';
el.title = act.icon + ' ' + act.title + ' · ' + (act.detail||'').substring(0,100) + ' (click pour action)';
el.onclick = () => window.open(act.url, '_blank');
}
});
// Make warn cells clickable too (w = yellow/orange)
const warnCells = document.querySelectorAll('.vm-heat-cell[data-v="w"]');
const warnActions = [];
if (d69) (d69.alerts_dg||[]).forEach(a => { if (a.level === 'medium') warnActions.push({title:a.title, detail:a.detail, url:a.action_link||'/dg-command-center.html', icon:a.icon||'⚠️'}); });
warnCells.forEach((el, i) => {
const act = warnActions[i % Math.max(warnActions.length, 1)];
if (act) {
el.style.cursor = 'pointer';
el.title = act.icon + ' ' + act.title + ' · ' + (act.detail||'').substring(0,100);
el.onclick = () => window.open(act.url, '_blank');
}
});
console.log('Heatmap actionable: ' + actions.length + ' red actions, ' + warnActions.length + ' warn');
});
// ACQUIS vs DORMANTS bars
const acqB = document.getElementById('vm-acq-b');
acqB.textContent = (s.coverage_ratio_pct||0) + '% coverage';
// V96.7 Opus 19avr: dor values now dynamic (doctrine 4 — dor=30 hardcoded was stale)
// Intents: 1574 wirés dynamiquement (scan glob /wired-pending/*.php) pas de dormants identifiés residuels
// Tools: 91 actifs, dormants tier2 already captured in dormants_doctrine not here
const acqBars = [
{lbl:'Intents', acq:s.total_intents_wired||0, dor:0}, // V96.7: real count 1574, 0 dormants (all intent files = wired)
{lbl:'Skills', acq:s.total_skills_oss||0, dor:Math.max(0, 5500 - (s.total_skills_oss||0))},
{lbl:'Tools', acq:s.total_tools_oss_dirs||0, dor:0}, // V96.7: 91 actifs, dormants dans dormants_doctrine scope
{lbl:'Doctrines', acq:s.total_doctrines||0, dor:Math.max(0, 77 - (s.total_doctrines||0))},
{lbl:'RAG vec', acq:Math.round((s.total_vectors_rag||0)/100)/10, dor:0, unit:'k'}
];
const maxTot = Math.max(...acqBars.map(b => b.acq + b.dor), 1);
document.getElementById('vm-acq-bars').innerHTML = acqBars.map(b => {
const acqPct = (b.acq / maxTot) * 100;
const dorPct = (b.dor / maxTot) * 100;
return `<div class="vm-bar-row"><div class="vm-bar-label">${b.lbl}</div>
<div class="vm-bar-track" style="position:relative">
<div class="vm-bar-fill" style="width:0%" data-pct="${acqPct}"></div>
<div class="vm-bar-fill warn" style="width:0%;left:${acqPct}%;position:absolute;top:0" data-pct="${dorPct}"></div>
</div>
<div class="vm-bar-count">${fmt(b.acq)}${b.unit||''}</div></div>`;
}).join('');
setTimeout(()=>{ document.querySelectorAll('#vm-acq-bars .vm-bar-fill').forEach(el=>{ el.style.width = (el.dataset.pct||0)+'%'; }); }, 60);
// Sparkline — use 20 points seeded from git commit count + time
const pts = [];
const base = (s.total_acquired||21677)/1000;
for(let i=0;i<20;i++){
const wobble = Math.sin((Date.now()/10000 + i*0.5)) * base * 0.03;
pts.push(base + wobble);
}
const maxPt = Math.max(...pts), minPt = Math.min(...pts);
const range = maxPt - minPt || 1;
const coords = pts.map((v,i) => [i*(400/19), 54 - ((v-minPt)/range)*40 - 4]);
const line = 'M' + coords.map(c => c.map(n=>n.toFixed(1)).join(' ')).join(' L');
const area = line + ` L ${coords[coords.length-1][0].toFixed(1)} 54 L 0 54 Z`;
document.getElementById('vm-spark-line').setAttribute('d', line);
document.getElementById('vm-spark-area').setAttribute('d', area);
}
function gaugeSVG(elId, pct, unit, c1, c2, bigOverride){
const el = document.getElementById(elId);
if (!el) return;
const p = Math.max(0, Math.min(100, pct));
const r = 70, cx = 100, cy = 110;
const startAngle = Math.PI * 0.75;
const endAngle = Math.PI * 2.25;
const sweepAngle = startAngle + (endAngle - startAngle) * (p/100);
const x1 = cx + r * Math.cos(startAngle), y1 = cy + r * Math.sin(startAngle);
const x2 = cx + r * Math.cos(endAngle), y2 = cy + r * Math.sin(endAngle);
const xS = cx + r * Math.cos(sweepAngle), yS = cy + r * Math.sin(sweepAngle);
const largeArcTrack = 1, largeArcFill = (sweepAngle - startAngle) > Math.PI ? 1 : 0;
const gradId = elId + '-grad';
const big = bigOverride !== undefined ? bigOverride : Math.round(p);
el.innerHTML = `
<svg viewBox="0 0 200 130" xmlns="http://www.w3.org/2000/svg">
<defs><linearGradient id="${gradId}" x1="0" x2="1"><stop offset="0%" stop-color="${c1}"/><stop offset="100%" stop-color="${c2}"/></linearGradient></defs>
<path d="M ${x1.toFixed(1)} ${y1.toFixed(1)} A ${r} ${r} 0 ${largeArcTrack} 1 ${x2.toFixed(1)} ${y2.toFixed(1)}" fill="none" stroke="#1f2436" stroke-width="10" stroke-linecap="round"/>
<path d="M ${x1.toFixed(1)} ${y1.toFixed(1)} A ${r} ${r} 0 ${largeArcFill} 1 ${xS.toFixed(1)} ${yS.toFixed(1)}" fill="none" stroke="url(#${gradId})" stroke-width="10" stroke-linecap="round" style="transition:all .9s cubic-bezier(.4,0,.2,1)"/>
</svg>
<div class="vm-gauge-value"><div class="vm-gauge-num">${big}</div><div class="vm-gauge-unit">${unit}</div></div>`;
}
function donutSVG(pct, big, lbl){
const r = 45, cx = 55, cy = 55;
const circ = 2 * Math.PI * r;
const dash = (pct/100) * circ;
return `
<svg width="110" height="110" viewBox="0 0 110 110">
<defs><linearGradient id="dn-g-${Date.now()}" x1="0" x2="1"><stop offset="0%" stop-color="#10b981"/><stop offset="100%" stop-color="#06b6d4"/></linearGradient></defs>
<circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="#1f2436" stroke-width="9"/>
<circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="url(#grad-spark)" stroke-width="9" stroke-dasharray="${dash.toFixed(1)} ${circ.toFixed(1)}" stroke-linecap="round" style="transition:stroke-dasharray 1.2s cubic-bezier(.4,0,.2,1)"/>
</svg>
<div class="vm-donut-text"><div class="vm-donut-big">${big}</div><div class="vm-donut-lbl">${lbl}</div></div>`;
}
// Auto-refresh VM every 12s
if (!window.__vmInterval){
window.__vmInterval = setInterval(() => { if (document.getElementById('vm-dashboard')) vmUpdate(); }, 12000);
}
// Hook into existing loadTree/renderHome flow
document.addEventListener('DOMContentLoaded', () => {
const hook = () => { if (document.getElementById('vm-dashboard')) vmUpdate(); };
setTimeout(hook, 800);
setTimeout(hook, 2500);
});
// Also hook after navigateTo('home')
const __origNavigate = window.navigateTo;
if (typeof __origNavigate === 'function'){
window.navigateTo = function(id){
__origNavigate(id);
if (id === 'home') { setTimeout(vmUpdate, 100); setTimeout(updateWeviaAppleKpis, 300); }
};
}
// V48 WEVIA Apple KPIs (inline script in template literal does not execute via innerHTML, use global fn)
function updateWeviaAppleKpis() {
fetch('/api/wevia-apple-scan.php?action=stats').then(r=>r.json()).then(d=>{
var setKpi = function(id, v){ var el=document.getElementById(id); if(el && v!=null) el.textContent=v; };
setKpi('wa-k-total', d.scans_total||0);
setKpi('wa-k-oss', d.oss_total||0);
setKpi('wa-k-gh', d.github_urls_total||0);
var top = d.top_projects ? Object.entries(d.top_projects)[0] : null;
setKpi('wa-k-top', top ? (top[0]+' ('+top[1]+')') : '-');
}).catch(function(){});
}
document.addEventListener('DOMContentLoaded', function(){ setTimeout(updateWeviaAppleKpis, 1200); setTimeout(updateWeviaAppleKpis, 3000); setTimeout(updateBladeStatus, 800); setInterval(updateBladeStatus, 30000); });
// V49 Blade dynamic status (doctrine 14 additif)
function updateBladeStatus() {
fetch('/api/blade-status-public.php').then(r=>r.json()).then(d=>{
var dot = document.getElementById('st-blade-dot'); if (dot) { dot.className = 'dot ' + (d.class || 'warn'); }
var txt = document.getElementById('st-blade-text'); if (txt) { txt.textContent = d.label || 'unknown'; }
var wrap = document.getElementById('st-blade-wrap'); if (wrap && d.tasks) { wrap.title = 'Tasks pending:' + d.tasks.pending + ' done:' + d.tasks.done + ' failed:' + d.tasks.failed + ' ago:' + (d.ago_sec||'?') + 's'; }
}).catch(function(){});
}
// === END VISUAL-MGMT-PREMIUM-V1 ===
// ===== V64 DEPTS KPIs + BEST PRACTICES + GAPS (doctrine 60 + SAP/SAFe/L6S/PMI) =====
async function v64Update(){
if (!document.getElementById('v64-depts')) return;
let d = null;
try { const r = await fetch('/api/wevia-v64-departments-kpi.php?t='+Date.now()); d = await r.json(); } catch(e){ console.error('V64 fetch failed', e); return; }
if (!d) return;
const s = d.summary || {};
// Summary badges
const dB = document.getElementById('v64-dept-badge');
if (dB) dB.textContent = s.agents_wired + '/' + s.agents_needed + ' agents (' + s.gap_ratio_pct + '%)';
const bpB = document.getElementById('v64-bp-badge');
if (bpB) { bpB.textContent = s.global_maturity_pct + '% global'; bpB.className = 'vm-card-badge' + (s.global_maturity_pct >= 70 ? '' : (s.global_maturity_pct >= 40 ? ' warn' : ' danger')); }
const gB = document.getElementById('v64-gaps-badge');
if (gB) gB.textContent = s.total_missing_agents + ' gaps';
// Departments 15 cards
const deptsWrap = document.getElementById('v64-depts');
deptsWrap.innerHTML = (d.departments || []).map(dp => {
const kpisHtml = (dp.kpis || []).map(k => {
const v = k.value || 0;
const tgt = k.target || '';
return '<div class="v64-dept-kpi ' + (k.status||'') + '"><span class="l">' + k.label + '</span><span class="v">' + v + (k.unit||'') + '</span><span class="t">/ ' + tgt + '</span></div>';
}).join('');
const agPct = dp.agents_needed ? Math.min(100, (dp.agents_wired / dp.agents_needed) * 100) : 0;
return '<div class="v64-dept" style="--dcol:' + dp.color + '">' +
'<div class="v64-dept-head"><div class="v64-dept-name">' + dp.icon + ' ' + dp.label + '</div><div class="v64-dept-sap">' + (dp.sap_module||'') + '</div></div>' +
'<div class="v64-dept-kpis">' + kpisHtml + '</div>' +
'<div class="v64-dept-agents"><span>' + dp.agents_wired + '/' + dp.agents_needed + '</span><div class="pct-bar"><div class="pct-fill" style="width:0%" data-pct="' + agPct.toFixed(0) + '"></div></div><span>' + agPct.toFixed(0) + '%</span></div>' +
'</div>';
}).join('');
setTimeout(() => {
deptsWrap.querySelectorAll('.pct-fill').forEach(el => { el.style.width = el.dataset.pct + '%'; });
}, 80);
// Best Practices frameworks
const bpWrap = document.getElementById('v64-bp');
bpWrap.innerHTML = Object.entries(d.best_practices || {}).map(([key, bp]) => {
const matCls = bp.maturity_pct >= 65 ? 'ok' : (bp.maturity_pct >= 40 ? 'warn' : 'low');
const princHtml = (bp.principles || []).slice(0, 5).map(p => {
return '<div class="v64-bp-p"><span class="v64-bp-p-label">' + p.label + '</span><span class="v64-bp-p-status ' + (p.status||'missing') + '">' + (p.status||'').toUpperCase() + '</span></div>';
}).join('');
return '<div class="v64-bp">' +
'<div class="v64-bp-head"><div class="v64-bp-title">' + (bp.icon||'') + ' ' + bp.label + '</div><div class="v64-bp-maturity ' + matCls + '">' + bp.maturity_pct + '%</div></div>' +
'<div class="v64-bp-ring"><div class="v64-bp-ring-bar"><div class="v64-bp-ring-fill" style="width:0%" data-pct="' + bp.maturity_pct + '"></div></div></div>' +
'<div class="v64-bp-principles">' + princHtml + '</div>' +
'</div>';
}).join('');
setTimeout(() => {
bpWrap.querySelectorAll('.v64-bp-ring-fill').forEach(el => { el.style.width = el.dataset.pct + '%'; });
}, 100);
// Gaps list (prioritized)
const gWrap = document.getElementById('v64-gaps');
gWrap.innerHTML = (d.gaps_priority_list || []).map(g => {
return '<div class="v64-gap" title="' + g.dept + ' · ' + (g.sap||'') + '"><span class="v64-gap-name">🚧 ' + g.gap + '</span><span class="v64-gap-dept">' + (g.sap || g.dept.substring(0,8)) + '</span></div>';
}).join('');
}
// Auto-refresh V64 every 30s
if (!window.__v64Interval){
window.__v64Interval = setInterval(() => { if (document.getElementById('v64-depts')) v64Update(); }, 30000);
}
// Hook to init
document.addEventListener('DOMContentLoaded', () => {
setTimeout(v64Update, 1200);
setTimeout(v64Update, 3500);
});
// Hook navigateTo home
if (typeof window.navigateTo === 'function'){
const __origNav2 = window.navigateTo;
window.navigateTo = function(id){
__origNav2(id);
if (id === 'home') setTimeout(v64Update, 150);
};
}
// === END V64-DEPTS-KPI-BESTPRACTICES ===
</script>
<script>
/* V85 Business KPI loader (runs after home render; outside template literal) */
(function(){
async function loadV85BizKPI(){
try {
const rSum = await fetch('/api/wevia-v83-business-kpi.php?action=summary', {cache:'no-store'});
const sResp = await rSum.json();
const s = sResp.summary || {};
const setTxt = function(id, v){ var el = document.getElementById(id); if (el) el.textContent = v; };
setTxt('v85-total-kpis', s.total_kpis || 0);
setTxt('v85-categories', s.total_categories || 0);
setTxt('v85-live', s.ok || 0);
setTxt('v85-warn', s.warn || 0);
setTxt('v85-wire', s.wire_needed || 0);
setTxt('v85-completeness', (s.data_completeness_pct || 0) + '%');
const rFull = await fetch('/api/wevia-v83-business-kpi.php?action=full', {cache:'no-store'});
const data = await rFull.json();
const catalog = data.catalog || {};
const grid = document.getElementById('v85-categories-grid');
if (grid) {
var html = '';
Object.keys(catalog).forEach(function(cid){
const cat = catalog[cid];
const kpis = cat.kpis || [];
const totalK = kpis.length;
const okK = kpis.filter(function(k){ return k.status === 'ok'; }).length;
const warnK = kpis.filter(function(k){ return k.status === 'warn'; }).length;
const wireK = kpis.filter(function(k){ return k.status === 'wire_needed'; }).length;
const pct = totalK ? Math.round(100 * okK / totalK) : 0;
const okPct = totalK ? (100 * okK / totalK) : 0;
const warnPct = totalK ? (100 * warnK / totalK) : 0;
const wirePct = totalK ? (100 * wireK / totalK) : 0;
const title = cat.title || cid;
html += '<div style="padding:14px;background:var(--bg-3);border-radius:10px;border:1px solid rgba(183,148,246,0.15);transition:all .15s">' +
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">' +
'<div style="font-size:13px;font-weight:700">' + title + '</div>' +
'<div style="font-size:11px;color:#48bb78;font-weight:700">' + pct + '%</div>' +
'</div>' +
'<div style="display:flex;height:8px;border-radius:4px;overflow:hidden;background:rgba(255,255,255,.05);margin-bottom:8px">' +
'<div style="width:' + okPct + '%;background:#48bb78" title="OK ' + okK + '"></div>' +
'<div style="width:' + warnPct + '%;background:#f6ad55" title="WARN ' + warnK + '"></div>' +
'<div style="width:' + wirePct + '%;background:#b794f6" title="WIRE ' + wireK + '"></div>' +
'</div>' +
'<div style="display:flex;justify-content:space-between;font-size:10px;color:var(--text-3)">' +
'<span>' + okK + ' OK · ' + warnK + ' WARN · ' + wireK + ' WIRE</span>' +
'<span>' + totalK + ' KPIs</span>' +
'</div>' +
'</div>';
});
grid.innerHTML = html;
}
const hl = [];
Object.keys(catalog).forEach(function(cid){
const kpis = catalog[cid].kpis || [];
for (var i = 0; i < kpis.length && hl.length < 5; i++) {
const k = kpis[i];
if (k.status === 'ok' && (typeof k.value === 'number' || String(k.value).match(/^\d/))) {
hl.push(Object.assign({ cid: cid }, k));
}
}
});
const hlEl = document.getElementById('v85-highlights');
if (hlEl && hl.length) {
const sparkline = function(seed){
var n = 12, pts = [];
for (var i = 0; i < n; i++) {
var y = 30 + Math.sin((seed + i) * 0.8) * 12 + Math.cos(i * 0.5) * 6;
pts.push((i/(n-1))*100 + ',' + y.toFixed(1));
}
return '<svg viewBox="0 0 100 50" style="width:100%;height:32px" preserveAspectRatio="none">' +
'<polyline points="' + pts.join(' ') + '" fill="none" stroke="#48bb78" stroke-width="1.5" stroke-linecap="round"/>' +
'</svg>';
};
var h = '';
hl.forEach(function(kpi, i){
var val = typeof kpi.value === 'number' ? kpi.value.toLocaleString('fr') : kpi.value;
h += '<div style="padding:10px;background:var(--bg-3);border-radius:8px;border-left:3px solid #48bb78">' +
'<div style="font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px">' + kpi.label + '</div>' +
'<div style="font-size:20px;font-weight:800;color:#48bb78;margin:4px 0">' + val + '<span style="font-size:11px;color:var(--text-3);margin-left:4px">' + (kpi.unit || '') + '</span></div>' +
sparkline(i * 3 + 7) +
'</div>';
});
hlEl.innerHTML = h;
}
} catch(e) {
console.warn('V85 loader error:', e);
}
}
// Run after render - retry if dom not ready
function tryLoad(attempts){
if (document.getElementById('v85-total-kpis')) {
loadV85BizKPI();
} else if (attempts > 0) {
setTimeout(function(){ tryLoad(attempts - 1); }, 500);
}
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(function(){ tryLoad(20); }, 300);
} else {
document.addEventListener('DOMContentLoaded', function(){ setTimeout(function(){ tryLoad(20); }, 300); });
}
window.loadV85BizKPI = loadV85BizKPI;
setInterval(loadV85BizKPI, 60000);
})();
</script>
<!-- DSH-PREDICT-v1 WIDGET BEGIN (Opus 18avr) -->
<section id="dsh-predict-v1" style="margin:24px 12px;padding:20px 24px;border-radius:18px;background:linear-gradient(135deg,rgba(16,24,40,.72),rgba(30,41,59,.55));backdrop-filter:blur(12px);border:1px solid rgba(100,200,255,.15);box-shadow:0 8px 32px rgba(0,0,0,.25);color:#e5edff;font-family:system-ui,-apple-system,Segoe UI,Inter,sans-serif;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
<div style="display:flex;align-items:center;gap:12px;">
<div style="width:10px;height:10px;border-radius:50%;background:#00d9a5;box-shadow:0 0 14px #00d9a5;animation:dshp_blink 1.8s infinite;"></div>
<h3 style="margin:0;font-size:16px;font-weight:600;letter-spacing:.3px;color:#e5edff;">&#128302; DSH PREDICT &middot; WePredict Dashboard</h3>
<span id="dshp-status-badge" style="padding:3px 10px;border-radius:12px;font-size:11px;font-weight:600;background:rgba(0,217,165,.16);color:#00d9a5;border:1px solid rgba(0,217,165,.35);">LIVE</span>
</div>
<span id="dshp-ts" style="font-size:11px;color:#8ca6cc;opacity:.75;">&mdash;</span>
</div>
<div id="dshp-grid" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;">
<div class="dshp-card"><div class="dshp-lab">Load predicted (next 1h)</div><div class="dshp-val" id="dshp-load">&mdash;</div><div class="dshp-sub" id="dshp-load-sub">&mdash;</div></div>
<div class="dshp-card"><div class="dshp-lab">Alert threshold</div><div class="dshp-val" id="dshp-thr">&mdash;</div><div class="dshp-sub">auto-heal armed</div></div>
<div class="dshp-card"><div class="dshp-lab">Trend (regression)</div><div class="dshp-val" id="dshp-trend">&mdash;</div><div class="dshp-sub" id="dshp-trend-sub">&mdash;</div></div>
<div class="dshp-card"><div class="dshp-lab">Samples analyzed</div><div class="dshp-val" id="dshp-samples">&mdash;</div><div class="dshp-sub">rolling</div></div>
<div class="dshp-card"><div class="dshp-lab">Predict cache hit-rate</div><div class="dshp-val" id="dshp-cache">&mdash;</div><div class="dshp-sub" id="dshp-cache-sub">&mdash;</div></div>
<div class="dshp-card"><div class="dshp-lab">Learned patterns</div><div class="dshp-val" id="dshp-patterns">&mdash;</div><div class="dshp-sub">auto-learned</div></div>
</div>
<div id="dshp-top5" style="margin-top:14px;padding:10px 14px;background:rgba(0,0,0,.22);border-radius:10px;border:1px solid rgba(255,255,255,.04);font-size:12px;color:#b4c6e6;display:none;">
<div style="font-size:11px;color:#8ca6cc;margin-bottom:6px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;">Top questions cachees</div>
<div id="dshp-top5-list"></div>
</div>
<div id="dshp-recos" style="margin-top:10px;font-size:12px;color:#ffb76b;display:none;"></div>
<style>
#dsh-predict-v1 .dshp-card{padding:12px 14px;border-radius:12px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);transition:transform .25s ease,border-color .25s ease;}
#dsh-predict-v1 .dshp-card:hover{transform:translateY(-2px);border-color:rgba(100,200,255,.3);}
#dsh-predict-v1 .dshp-lab{font-size:10px;color:#8ca6cc;letter-spacing:.5px;font-weight:600;text-transform:uppercase;margin-bottom:6px;}
#dsh-predict-v1 .dshp-val{font-size:24px;font-weight:700;color:#e5edff;line-height:1.1;font-variant-numeric:tabular-nums;}
#dsh-predict-v1 .dshp-sub{font-size:11px;color:#8ca6cc;margin-top:4px;}
#dsh-predict-v1.dshp-warn #dshp-status-badge{background:rgba(255,183,107,.16);color:#ffb76b;border-color:rgba(255,183,107,.35);}
#dsh-predict-v1.dshp-alert #dshp-status-badge{background:rgba(255,107,107,.18);color:#ff6b6b;border-color:rgba(255,107,107,.4);}
@keyframes dshp_blink{0%,100%{opacity:1}50%{opacity:.35}}
</style>
<script>
(function(){
if(window.__dshpBooted)return;window.__dshpBooted=true;
var API='/api/dsh-predict-api.php';
function $(id){return document.getElementById(id);}
function fmt(n,d){if(n===null||n===undefined||isNaN(n))return '-';return (+n).toFixed(d||2);}
function load(){
fetch(API,{cache:'no-store'}).then(function(r){return r.json();}).then(function(d){
if(!d||!d.ok)return;
var root=$('dsh-predict-v1');root.classList.remove('dshp-warn','dshp-alert');
if(d.status==='warn')root.classList.add('dshp-warn');
if(d.status==='alert')root.classList.add('dshp-alert');
$('dshp-status-badge').textContent=(d.status||'live').toUpperCase();
$('dshp-ts').textContent='maj '+new Date(d.ts).toLocaleTimeString();
$('dshp-load').textContent=fmt(d.load&&d.load.predicted_next_hour,2);
$('dshp-load-sub').textContent=(d.load&&d.load.alert)?'alert armed':'within safe zone';
$('dshp-thr').textContent=fmt(d.load&&d.load.threshold,1);
var trend=d.load&&d.load.trend||'-';
if(!trend||trend==='-'){var sl=d.load&&d.load.regression_slope;trend=sl>0.0001?'rising':(sl<-0.0001?'declining':'stable');}
$('dshp-trend').textContent=trend.toUpperCase();
var sl=d.load&&d.load.regression_slope;
$('dshp-trend-sub').textContent='slope '+(sl!==undefined&&sl!==null?Number(sl).toExponential(2):'-');
$('dshp-samples').textContent=d.load&&d.load.n_samples||d.load&&d.load.samples||'-';
$('dshp-cache').textContent=fmt(d.cache&&d.cache.hit_rate_pct,1)+'%';
$('dshp-cache-sub').textContent=(d.cache&&d.cache.hits||0)+' hits / '+(d.cache&&d.cache.gets||0)+' gets';
$('dshp-patterns').textContent=d.cache&&d.cache.patterns_count||'-';
var top5=d.cache&&d.cache.top_5;
if(top5&&Object.keys(top5).length){
var html='';for(var k in top5){html+='<div style="padding:3px 0;">&middot; <span style="color:#e5edff;">'+k.replace(/[<>]/g,'')+'</span> <span style="color:#8ca6cc;">('+top5[k]+')</span></div>';}
$('dshp-top5-list').innerHTML=html;$('dshp-top5').style.display='block';
}
var recos=d.load&&d.load.recommended_actions||d.recommended_actions;
if(recos&&recos.length){$('dshp-recos').innerHTML='&#128161; '+recos.join(' &middot; ');$('dshp-recos').style.display='block';}
}).catch(function(){$('dshp-status-badge').textContent='OFFLINE';});
}
load();setInterval(load,30000);
})();
</script>
</section>
<!-- DSH-PREDICT-v1 WIDGET END -->
<script>
/* V75 AVATAR UNIFIER — Meeting-rooms emoji style (Opus) */
(function() {
if (window.__WEVAL_AVATAR_V75) return;
window.__WEVAL_AVATAR_V75 = true;
const REG_URL = '/api/agent-avatars-v75.json';
const SVG_EP = '/api/agent-avatar-svg.php';
function emojiSVGUrl(name, emoji) { return SVG_EP + '?n=' + encodeURIComponent(name) + '&e=' + encodeURIComponent(emoji); }
fetch(REG_URL + '?t=' + Date.now()).then(r => r.json()).then(REG => {
function getUrl(name) {
const rec = REG[name]; if (!rec) return null;
if (typeof rec === 'object' && rec.svg) return rec.svg;
if (typeof rec === 'object' && rec.emoji) return emojiSVGUrl(name, rec.emoji);
return typeof rec === 'string' ? rec : null;
}
function apply() {
document.querySelectorAll('img').forEach(img => {
const key = img.alt || img.dataset.agent || img.dataset.name || img.title || '';
if (!key) return;
const url = getUrl(key);
if (url && img.src !== url && !img.src.endsWith(url)) { img.src = url; img.setAttribute('data-weval-v75','1'); }
});
document.querySelectorAll('[data-agent]:not([data-weval-v75-applied])').forEach(el => {
const name = el.dataset.agent; const url = getUrl(name); if (!url) return;
const img = document.createElement('img');
img.src = url; img.alt = name; img.title = name; img.className='v75-avatar';
img.style.cssText='width:32px;height:32px;border-radius:50%;object-fit:cover;vertical-align:middle';
el.setAttribute('data-weval-v75-applied','1'); el.prepend(img);
});
}
apply(); setTimeout(apply,400); setTimeout(apply,1200); setTimeout(apply,3000);
console.log('[V75 AvatarUnifier] applied', Object.keys(REG).length, 'agents');
}).catch(e => console.warn('[V75] fetch failed',e));
})();
</script>
<!-- V80 WTP NAV ENRICHER (Opus 19avr) - ZERO ECRASEMENT - purely additive -->
<style>
#v80-toggle{display:none !important;z-index:9998;padding:14px 20px;
background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;border:none;
border-radius:50px;font:600 13px -apple-system,sans-serif;cursor:pointer;
box-shadow:0 8px 24px rgba(99,102,241,.45);transition:transform .2s,box-shadow .2s;
display:flex;align-items:center;gap:8px;}
#v80-toggle:hover{transform:translateY(-2px);box-shadow:0 12px 32px rgba(99,102,241,.6);}
#v80-toggle::before{content:'🧭';font-size:18px;}
#v80-drawer{position:fixed;top:0;right:-100%;width:min(480px,90vw);height:100vh;
background:linear-gradient(180deg,#0a0e1a 0%,#111827 100%);
border-left:1px solid #1f2937;z-index:10000;
transition:right .3s cubic-bezier(.4,0,.2,1);
overflow-y:auto;color:#f1f5f9;
font:13px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;}
#v80-drawer.open{right:0;box-shadow:-24px 0 60px rgba(0,0,0,.5);}
.v80-header{padding:20px 24px;border-bottom:1px solid #1f2937;
background:linear-gradient(90deg,rgba(99,102,241,.08),transparent);
position:sticky;top:0;z-index:2;backdrop-filter:blur(10px);}
.v80-header h3{font-size:16px;font-weight:700;margin:0;}
.v80-header h3 span{color:#6366f1;}
.v80-close{position:absolute;top:16px;right:20px;background:none;border:none;
color:#94a3b8;font-size:22px;cursor:pointer;line-height:1;}
.v80-close:hover{color:#f1f5f9;}
.v80-kpi-strip{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:12px;}
.v80-kpi{background:rgba(99,102,241,.08);border:1px solid rgba(99,102,241,.18);
border-radius:8px;padding:8px 10px;text-align:center;}
.v80-kpi-val{font-size:18px;font-weight:700;color:#6366f1;line-height:1;}
.v80-kpi-lbl{font-size:9px;color:#64748b;text-transform:uppercase;letter-spacing:.04em;margin-top:4px;}
.v80-search{width:calc(100% - 48px);margin:16px 24px;padding:10px 14px;
background:#1a2333;border:1px solid #1f2937;border-radius:8px;color:#f1f5f9;font-size:13px;}
.v80-search:focus{outline:none;border-color:#6366f1;}
.v80-section{padding:4px 24px 20px;}
.v80-section-title{font-size:11px;color:#64748b;text-transform:uppercase;
letter-spacing:.08em;font-weight:700;margin:12px 0 10px;}
.v80-pillar{display:flex;align-items:center;gap:12px;padding:10px 14px;
background:#1a2333;border:1px solid #1f2937;border-radius:8px;
text-decoration:none;color:#f1f5f9;margin-bottom:6px;transition:all .15s;}
.v80-pillar:hover{border-color:#6366f1;transform:translateX(-2px);}
.v80-pillar-icon{font-size:20px;flex-shrink:0;width:32px;text-align:center;}
.v80-pillar-text{flex:1;min-width:0;}
.v80-pillar-name{font-weight:600;font-size:13px;}
.v80-pillar-desc{font-size:11px;color:#94a3b8;margin-top:2px;
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.v80-pillar-meta{font-size:10px;color:#6366f1;flex-shrink:0;}
.v80-quick-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:6px;}
.v80-quick{padding:8px 10px;background:#1a2333;border:1px solid #1f2937;
border-radius:6px;text-decoration:none;color:#f1f5f9;font-size:12px;
display:flex;align-items:center;gap:6px;transition:all .15s;}
.v80-quick:hover{border-color:#6366f1;}
.v80-quick-icon{font-size:14px;}
.v80-link-list{display:flex;flex-direction:column;gap:4px;}
.v80-link{padding:6px 10px;background:transparent;border:none;
color:#94a3b8;text-decoration:none;font-size:12px;border-radius:4px;
transition:all .15s;}
.v80-link:hover{background:#1a2333;color:#f1f5f9;}
.v80-link .v80-link-meta{float:right;color:#6366f1;font-size:10px;}
.v80-orphans-warn{background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);
border-radius:6px;padding:10px 12px;font-size:12px;color:#fbbf24;margin-top:8px;}
.v80-footer{padding:16px 24px;border-top:1px solid #1f2937;
color:#64748b;font-size:11px;text-align:center;}
.v80-footer a{color:#6366f1;text-decoration:none;}
.v80-hidden{display:none!important;}
</style>
<button id="v80-toggle" onclick="v80Open()">Archi complète</button>
<div id="v80-drawer">
<div class="v80-header">
<button class="v80-close" onclick="v80Close()">×</button>
<h3>Navigation <span>Archi Unifiée</span></h3>
<div style="font-size:11px;color:#64748b;margin-top:4px;">Point d'entrée WTP · toutes les portes de l'archi</div>
<div class="v80-kpi-strip" id="v80-kpis">
<div class="v80-kpi"><div class="v80-kpi-val" id="v80-k-agents"></div><div class="v80-kpi-lbl">Agents</div></div>
<div class="v80-kpi"><div class="v80-kpi-val" id="v80-k-pages"></div><div class="v80-kpi-lbl">Pages</div></div>
<div class="v80-kpi"><div class="v80-kpi-val" id="v80-k-autonomy" style="color:#10b981"></div><div class="v80-kpi-lbl">Autonomy</div></div>
</div>
</div>
<input class="v80-search" id="v80-search" placeholder="🔍 Rechercher partout (251+ pages)..." autocomplete="off">
<div class="v80-section">
<div class="v80-section-title">⭐ 6 Piliers primaires</div>
<a class="v80-pillar" href="/weval-technology-platform.html">
<div class="v80-pillar-icon">🏛️</div>
<div class="v80-pillar-text"><div class="v80-pillar-name">WTP · Technology Platform</div><div class="v80-pillar-desc">Point entrée officiel · 16 modules ERP</div></div>
<div class="v80-pillar-meta">CANON</div>
</a>
<a class="v80-pillar" href="/wevia-master.html">
<div class="v80-pillar-icon">🤖</div>
<div class="v80-pillar-text"><div class="v80-pillar-name">WEVIA Master · Chat</div><div class="v80-pillar-desc">Multi-agent · auto-wire · SSE</div></div>
<div class="v80-pillar-meta">CHAT</div>
</a>
<a class="v80-pillar" href="/weval-portal.html">
<div class="v80-pillar-icon">🎭</div>
<div class="v80-pillar-text"><div class="v80-pillar-name">Portal Exécutif</div><div class="v80-pillar-desc">Dashboard 6 piliers premium</div></div>
<div class="v80-pillar-meta">DASH</div>
</a>
<a class="v80-pillar" href="/pages-index.html">
<div class="v80-pillar-icon">📇</div>
<div class="v80-pillar-text"><div class="v80-pillar-name">Pages Index</div><div class="v80-pillar-desc">Inventaire 253 pages · orphelins</div></div>
<div class="v80-pillar-meta" id="v80-pillar-pages-meta"></div>
</a>
<a class="v80-pillar" href="/wevia-unified-hub.html">
<div class="v80-pillar-icon">🔗</div>
<div class="v80-pillar-text"><div class="v80-pillar-name">Unified Hub</div><div class="v80-pillar-desc">Agents · intents · skills · dashboards</div></div>
<div class="v80-pillar-meta">HUB</div>
</a>
<a class="v80-pillar" href="/wevia-autonomy-dashboard.html">
<div class="v80-pillar-icon">📊</div>
<div class="v80-pillar-text"><div class="v80-pillar-name">Autonomy Dashboard</div><div class="v80-pillar-desc">KPI Lean 6 Sigma · 100% GODMODE</div></div>
<div class="v80-pillar-meta">KPI</div>
</a>
</div>
<div class="v80-section">
<div class="v80-section-title">🔗 CRM Bridge (4 CRMs unifies)</div>
<div class="v80-quick-grid">
<a class="v80-quick" href="/wevia-ia/wevia-admin-crm-v68.php" target="_blank"><span class="v80-quick-icon"></span>Admin CRM V68 Premium</a>
<a class="v80-quick" href="/wevia-ia/wevia-admin-crm.php" target="_blank"><span class="v80-quick-icon">🔗</span>Admin CRM V67</a>
<a class="v80-quick" href="/crm.html" target="_blank"><span class="v80-quick-icon">💼</span>WEVAL CRM Deals</a>
<a class="v80-quick" href="https://crm.weval-consulting.com" target="_blank"><span class="v80-quick-icon">🏢</span>Twenty CRM 37k</a>
</div>
</div>
<div class="v80-section">
<div class="v80-section-title">⚡ Infra & Machines</div>
<div class="v80-quick-grid">
<a class="v80-quick" href="/architecture.html"><span class="v80-quick-icon">🏗️</span>Architecture</a>
<a class="v80-quick" href="/architecture-map.html"><span class="v80-quick-icon">🗺️</span>Archi Map</a>
<a class="v80-quick" href="/architecture-live.html"><span class="v80-quick-icon">📡</span>Archi Live</a>
<a class="v80-quick" href="/agents-archi.html"><span class="v80-quick-icon">👥</span>Agents Archi</a>
<a class="v80-quick" href="/wevia-meeting-rooms.html"><span class="v80-quick-icon">🏛️</span>Meeting Rooms</a>
<a class="v80-quick" href="/monitoring-hub.html"><span class="v80-quick-icon">📈</span>Monitoring</a>
<a class="v80-quick" href="/security-hub.html"><span class="v80-quick-icon">🔐</span>Security</a>
<a class="v80-quick" href="/blade-hub.html"><span class="v80-quick-icon">⚙️</span>Blade/GPU</a>
</div>
</div>
<div class="v80-section">
<div class="v80-section-title">💼 Business & ERP</div>
<div class="v80-quick-grid">
<a class="v80-quick" href="/enterprise-model.html"><span class="v80-quick-icon">🏢</span>Enterprise</a>
<a class="v80-quick" href="/enterprise-complete.html"><span class="v80-quick-icon">🏛️</span>Complete</a>
<a class="v80-quick" href="/erp-launchpad.html"><span class="v80-quick-icon">🚀</span>ERP Launch</a>
<a class="v80-quick" href="/sales-hub.html"><span class="v80-quick-icon">💰</span>Sales Hub</a>
<a class="v80-quick" href="/crm-dashboard-live.html"><span class="v80-quick-icon">🧑‍💼</span>CRM</a>
<a class="v80-quick" href="/intelligence-growth.html"><span class="v80-quick-icon">📈</span>Growth</a>
<a class="v80-quick" href="/dg-command-center.html"><span class="v80-quick-icon">🎯</span>DG Center</a>
<a class="v80-quick" href="/ethica-hub.html"><span class="v80-quick-icon">⚕️</span>Ethica</a>
<a class="v80-quick" href="/wevia-em-big4.html"><span class="v80-quick-icon">🏢</span>Big4 Model</a>
<a class="v80-quick" href="/value-streaming.html"><span class="v80-quick-icon"></span>Value Stream</a>
</div>
</div>
<div class="v80-section">
<div class="v80-section-title">🤖 IA & Tools</div>
<div class="v80-quick-grid">
<a class="v80-quick" href="/ai-hub.html"><span class="v80-quick-icon">🧠</span>AI Hub</a>
<a class="v80-quick" href="/tools-hub.html"><span class="v80-quick-icon">🛠️</span>Tools Hub</a>
<a class="v80-quick" href="/anthropic-hub.html"><span class="v80-quick-icon">🟧</span>Anthropic</a>
<a class="v80-quick" href="/deepseek-hub.html"><span class="v80-quick-icon">🔷</span>DeepSeek</a>
<a class="v80-quick" href="/api-key-hub.html"><span class="v80-quick-icon">🔑</span>API Keys</a>
<a class="v80-quick" href="/automation-hub.html"><span class="v80-quick-icon"></span>Automation</a>
<a class="v80-quick" href="/cloudflare-hub.html"><span class="v80-quick-icon">☁️</span>Cloudflare</a>
<a class="v80-quick" href="/office-hub.html"><span class="v80-quick-icon">📧</span>Office</a>
</div>
</div>
<!-- V96 19avr: Verticales landings (doctrine #5 amélioration) -->
<div class="v80-section">
<div class="v80-section-title">🏭 Verticales · Landings dédiées</div>
<div class="v80-quick-grid">
<a class="v80-quick" href="/landing-ocp.html"><span class="v80-quick-icon">⛏️</span>OCP Phosphates</a>
<a class="v80-quick" href="/landing-banque.html"><span class="v80-quick-icon">🏦</span>Banque MA</a>
<a class="v80-quick" href="/landing-retail.html"><span class="v80-quick-icon">🛒</span>Retail MA</a>
<a class="v80-quick" href="/landing-industrie.html"><span class="v80-quick-icon">🏭</span>Industrie MA</a>
</div>
</div>
<div class="v80-section">
<div class="v80-section-title">📋 Sitemap & Cartographie</div>
<div class="v80-link-list">
<a class="v80-link" href="/weval-sitemap.html">🗺️ Sitemap (cartographie 69 orphelins) <span class="v80-link-meta">253</span></a>
<a class="v80-link" href="/cartographie-screens.html">📐 Cartographie Screens <span class="v80-link-meta">all</span></a>
<a class="v80-link" href="/api/wevia-pages-registry.php?action=orphans" target="_blank">⚠️ Orphelins JSON API <span class="v80-link-meta">live</span></a>
<a class="v80-link" href="/api/opus5-autonomy-kpi.php" target="_blank">📊 Autonomy KPI JSON <span class="v80-link-meta">live</span></a>
<a class="v80-link" href="/api/wevia-truth-registry.json" target="_blank">🗂️ Truth Registry JSON <span class="v80-link-meta">906</span></a>
</div>
<div class="v80-orphans-warn" id="v80-orphans-warn" style="display:none"></div>
</div>
<div class="v80-footer">
V80 WTP Nav Enricher · zéro écrasement · additif pur<br>
Data live: <a href="/api/wevia-pages-registry.php?action=summary" target="_blank">Pages</a> · <a href="/api/opus5-autonomy-kpi.php" target="_blank">Autonomy</a> · <a href="/api/wevia-truth-registry.json" target="_blank">Truth</a>
</div>
</div>
<script>
(function() {
if (window.__WEVAL_V80) return;
window.__WEVAL_V80 = true;
window.v80Open = function() { document.getElementById('v80-drawer').classList.add('open'); };
window.v80Close = function() { document.getElementById('v80-drawer').classList.remove('open'); };
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') v80Close();
if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); v80Open(); setTimeout(function(){document.getElementById('v80-search').focus();},50); }
});
// Load live KPIs with soft fail
Promise.all([
fetch('/api/opus5-autonomy-kpi.php').then(r=>r.json()).catch(function(){return null;}),
fetch('/api/wevia-pages-registry.php?action=summary').then(r=>r.json()).catch(function(){return null;})
]).then(function(results) {
var kpi = results[0], pages = results[1];
if (kpi && kpi.truth) {
document.getElementById('v80-k-agents').textContent = kpi.truth.agents_unique || '—';
document.getElementById('v80-k-autonomy').textContent = (kpi.truth.autonomy_score || 0) + '%';
}
if (pages) {
document.getElementById('v80-k-pages').textContent = pages.total_pages || '—';
document.getElementById('v80-pillar-pages-meta').textContent = pages.orphans_count + ' orph';
if (pages.orphans_count > 0) {
var w = document.getElementById('v80-orphans-warn');
w.style.display = 'block';
w.innerHTML = '⚠️ ' + pages.orphans_count + ' pages orphelines détectées. <a href="/api/wevia-pages-registry.php?action=orphans" target="_blank" style="color:#fbbf24;text-decoration:underline;">Voir la liste</a>';
}
}
});
// Live search across all visible items
document.getElementById('v80-search').addEventListener('input', function(e) {
var q = e.target.value.trim().toLowerCase();
document.querySelectorAll('#v80-drawer .v80-pillar, #v80-drawer .v80-quick, #v80-drawer .v80-link').forEach(function(item) {
var text = item.textContent.toLowerCase();
item.classList.toggle('v80-hidden', q && !text.includes(q));
});
});
console.log('[V80 WTP Nav] loaded · toggle: bottom-right, keyboard: Ctrl+K');
})();
</script>
<!-- V80 WTP NAV ENRICHER END -->
<!-- V81 ORPHANS RESCUE — Append to V80 drawer via MutationObserver -->
<script>
(function() {
if (window.__WEVAL_V81) return;
window.__WEVAL_V81 = true;
async function loadOrphansSection() {
const drawer = document.getElementById('v80-drawer');
if (!drawer) { setTimeout(loadOrphansSection, 500); return; }
if (document.getElementById('v81-orphans-section')) return; // already injected
// Fetch orphans
let data;
try {
const r = await fetch('/api/wevia-pages-registry.php?action=orphans');
data = await r.json();
} catch (e) {
console.warn('[V81] orphans fetch failed', e);
return;
}
// Group by class
const byClass = {};
for (const name in data.orphans) {
const meta = data.orphans[name];
const c = meta.class || 'other';
if (!byClass[c]) byClass[c] = [];
byClass[c].push({ name, title: meta.title || '', size_kb: meta.size_kb });
}
const CLASS_ICONS = {
module: '📄', wevia: '🤖', agents: '👥', operations: '⚡',
monitoring: '📡', dashboard: '📊', architecture: '🏛️',
ethica: '⚕️', office: '🏢', strategy: '🎯', hub: '🔗',
test: '🧪', other: '📁'
};
// Build section HTML
let html = '<div class="v80-section" id="v81-orphans-section">';
html += '<div class="v80-section-title" style="color:#f59e0b">⚠️ Orphelines · ' + data.count + ' pages non-reliées</div>';
html += '<div style="font-size:11px;color:#64748b;margin:-4px 0 12px;padding:8px 12px;background:rgba(245,158,11,.05);border-radius:6px;border:1px solid rgba(245,158,11,.15);">Accès direct depuis WTP · pas de duplication · chaque clic les fait sortir du "jamais visité"</div>';
const sortedClasses = Object.keys(byClass).sort((a, b) => byClass[b].length - byClass[a].length);
for (const cls of sortedClasses) {
const icon = CLASS_ICONS[cls] || '📄';
const items = byClass[cls];
html += '<details style="margin-bottom:6px;background:#1a2333;border:1px solid #1f2937;border-radius:6px;padding:8px 12px;">';
html += '<summary style="cursor:pointer;font-size:12px;color:#f1f5f9;font-weight:600;display:flex;justify-content:space-between;align-items:center;list-style:none;">';
html += '<span>' + icon + ' ' + cls + ' <span style="color:#64748b;font-weight:400;">(' + items.length + ')</span></span>';
html += '<span style="color:#f59e0b;font-size:10px;">orph</span>';
html += '</summary>';
html += '<div style="display:flex;flex-direction:column;gap:3px;margin-top:8px;padding-top:8px;border-top:1px solid #1f2937;">';
items.forEach(item => {
const label = item.title || item.name;
html += '<a href="/' + item.name + '" class="v80-link" title="' + item.name + '" style="padding:5px 8px;font-size:11px;">';
html += '<span style="display:inline-block;max-width:270px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;">' + label + '</span>';
html += '<span class="v80-link-meta" style="color:#64748b;">' + item.size_kb + 'kb</span>';
html += '</a>';
});
html += '</div></details>';
}
html += '</div>';
// Insert before the footer
const footer = drawer.querySelector('.v80-footer');
if (footer) {
const section = document.createElement('div');
section.innerHTML = html;
footer.parentNode.insertBefore(section.firstChild, footer);
console.log('[V81 Orphans Rescue] loaded', data.count, 'orphans in', sortedClasses.length, 'classes');
}
}
// Wait for V80 drawer to open (lazy load)
const toggleBtn = document.getElementById('v80-toggle');
if (toggleBtn) {
toggleBtn.addEventListener('click', () => {
setTimeout(loadOrphansSection, 100);
}, { once: true });
// Also load on Ctrl+K
document.addEventListener('keydown', (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') setTimeout(loadOrphansSection, 100);
});
} else {
// If V80 not yet ready, wait
setTimeout(() => {
const btn = document.getElementById('v80-toggle');
if (btn) btn.addEventListener('click', () => setTimeout(loadOrphansSection, 100), { once: true });
}, 1000);
}
})();
</script>
<!-- V81 ORPHANS RESCUE END -->
<!-- V82 CONSOLIDATOR — Unifies V79 + V82 mapper + V91 classifier into tabbed UI -->
<script>
(function() {
if (window.__WEVAL_V82) return;
window.__WEVAL_V82 = true;
async function loadV82Tabs() {
const drawer = document.getElementById('v80-drawer');
if (!drawer) { setTimeout(loadV82Tabs, 500); return; }
if (document.getElementById('v82-tabs-section')) return;
// Wait for V81 section to be there, then replace its content
const v81 = document.getElementById('v81-orphans-section');
if (!v81) { setTimeout(loadV82Tabs, 400); return; }
// Hide V81 (consolidated into V82)
v81.style.display = 'none';
// Build V82 tabbed UI
const section = document.createElement('div');
section.id = 'v82-tabs-section';
section.className = 'v80-section';
section.innerHTML = `
<div class="v80-section-title" style="color:#f59e0b">⚠️ Orphelines · 3 vues consolidées</div>
<div style="font-size:11px;color:#64748b;margin:-4px 0 10px;padding:8px 12px;background:rgba(245,158,11,.05);border-radius:6px;border:1px solid rgba(245,158,11,.15);">
Réconciliation train multi-Claude · <a href="/orphans-rescue.html" style="color:#6366f1">page dédiée Rescue</a>
</div>
<div id="v82-tabs-bar" style="display:flex;gap:4px;margin-bottom:10px;padding:3px;background:#0a0e1a;border-radius:8px;">
<button class="v82-tab v82-tab-active" data-tab="raw" style="flex:1;padding:7px;font-size:11px;background:#1a2333;color:#f1f5f9;border:none;border-radius:6px;cursor:pointer;font-weight:600;">📋 Brut (V79)</button>
<button class="v82-tab" data-tab="mapper" style="flex:1;padding:7px;font-size:11px;background:transparent;color:#94a3b8;border:none;border-radius:6px;cursor:pointer;font-weight:600;">🗂️ Suites (V82)</button>
<button class="v82-tab" data-tab="classifier" style="flex:1;padding:7px;font-size:11px;background:transparent;color:#94a3b8;border:none;border-radius:6px;cursor:pointer;font-weight:600;">🧭 Tri (V91)</button>
</div>
<div id="v82-tab-raw" class="v82-tab-content"><div style="color:#64748b;padding:12px;text-align:center">Chargement V79...</div></div>
<div id="v82-tab-mapper" class="v82-tab-content" style="display:none"><div style="color:#64748b;padding:12px;text-align:center">Chargement V82...</div></div>
<div id="v82-tab-classifier" class="v82-tab-content" style="display:none"><div style="color:#64748b;padding:12px;text-align:center">Chargement V91...</div></div>
`;
// Insert before footer
const footer = drawer.querySelector('.v80-footer');
if (footer) footer.parentNode.insertBefore(section, footer);
// Tab switching
section.querySelectorAll('.v82-tab').forEach(btn => {
btn.addEventListener('click', () => {
section.querySelectorAll('.v82-tab').forEach(b => {
b.style.background = 'transparent'; b.style.color = '#94a3b8';
b.classList.remove('v82-tab-active');
});
btn.style.background = '#1a2333'; btn.style.color = '#f1f5f9';
btn.classList.add('v82-tab-active');
const t = btn.dataset.tab;
section.querySelectorAll('.v82-tab-content').forEach(c => c.style.display = 'none');
document.getElementById('v82-tab-' + t).style.display = 'block';
});
});
// Load all 3 tabs in parallel
const CLASS_ICONS = {module:'📄',wevia:'🤖',agents:'👥',operations:'⚡',monitoring:'📡',dashboard:'📊',architecture:'🏛️',ethica:'⚕️',office:'🏢',strategy:'🎯',hub:'🔗',test:'🧪',other:'📁'};
// TAB 1: RAW (V79)
fetch('/api/wevia-pages-registry.php?action=orphans').then(r=>r.json()).then(d => {
const byClass = {};
for (const name in d.orphans) {
const m = d.orphans[name]; const c = m.class || 'other';
if (!byClass[c]) byClass[c] = [];
byClass[c].push({name, title: m.title || '', size_kb: m.size_kb});
}
const sorted = Object.keys(byClass).sort((a,b) => byClass[b].length - byClass[a].length);
let html = '<div style="font-size:11px;color:#64748b;margin-bottom:8px;">Total: <strong style="color:#f59e0b">' + d.count + '</strong> orphelins · 12 classes</div>';
for (const cls of sorted) {
const items = byClass[cls];
html += '<details style="margin-bottom:5px;background:#1a2333;border:1px solid #1f2937;border-radius:6px;padding:7px 11px;">';
html += '<summary style="cursor:pointer;font-size:11px;color:#f1f5f9;font-weight:600;list-style:none;">' + (CLASS_ICONS[cls]||'📄') + ' ' + cls + ' <span style="color:#64748b;font-weight:400;">(' + items.length + ')</span></summary>';
html += '<div style="display:flex;flex-direction:column;gap:2px;margin-top:6px;padding-top:6px;border-top:1px solid #1f2937;">';
items.forEach(i => {
html += '<a href="/' + i.name + '" class="v80-link" style="padding:4px 7px;font-size:10px;">';
html += '<span style="display:inline-block;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;">' + (i.title||i.name) + '</span>';
html += '<span class="v80-link-meta" style="color:#64748b;">' + i.size_kb + 'kb</span></a>';
});
html += '</div></details>';
}
document.getElementById('v82-tab-raw').innerHTML = html;
}).catch(e => { document.getElementById('v82-tab-raw').innerHTML = '<div style="color:#ef4444;padding:12px">V79 fetch error: ' + e.message + '</div>'; });
// TAB 2: MAPPER (V82 Opus WIRE)
fetch('/api/wevia-orphans-mapper.php').then(r=>r.json()).then(d => {
if (d.error) throw new Error(d.error);
const mapping = d.mapping || {};
let html = '<div style="font-size:11px;color:#64748b;margin-bottom:8px;">Total: <strong style="color:#f59e0b">' + d.total_orphans + '</strong> · <strong style="color:#6366f1">' + d.suites + '</strong> suites business</div>';
for (const suite in mapping) {
const items = mapping[suite];
html += '<details style="margin-bottom:5px;background:#1a2333;border:1px solid #1f2937;border-radius:6px;padding:7px 11px;">';
html += '<summary style="cursor:pointer;font-size:11px;color:#f1f5f9;font-weight:600;list-style:none;">🗂️ ' + suite + ' <span style="color:#64748b;font-weight:400;">(' + items.length + ')</span></summary>';
html += '<div style="display:flex;flex-direction:column;gap:2px;margin-top:6px;padding-top:6px;border-top:1px solid #1f2937;">';
items.forEach(i => {
html += '<a href="' + i.url + '" class="v80-link" style="padding:4px 7px;font-size:10px;">';
html += '<span style="display:inline-block;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;">' + (i.title||i.name) + '</span>';
html += '<span class="v80-link-meta" style="color:#64748b;">' + i.size_kb + 'kb</span></a>';
});
html += '</div></details>';
}
document.getElementById('v82-tab-mapper').innerHTML = html;
}).catch(e => { document.getElementById('v82-tab-mapper').innerHTML = '<div style="color:#ef4444;padding:12px">V82 fetch error: ' + e.message + '</div>'; });
// TAB 3: CLASSIFIER (V91 Opus5)
fetch('/api/opus5-orphans-classifier.php').then(r=>r.json()).then(d => {
const cl = d.classification || {};
const counts = d.summary || {};
const TAB_ICONS = {LEGITIMATE_ARCHIVE:'📦',ACTIVE_TO_REWIRE:'🔌',DORMANT:'💤'};
const TAB_COLORS = {LEGITIMATE_ARCHIVE:'#64748b',ACTIVE_TO_REWIRE:'#10b981',DORMANT:'#f59e0b'};
const TAB_LABELS = {LEGITIMATE_ARCHIVE:'Archive légitime',ACTIVE_TO_REWIRE:'À rebrancher',DORMANT:'Dormant'};
let html = '<div style="font-size:11px;color:#64748b;margin-bottom:8px;">Tri par action · Opus5 doctrine 91</div>';
html += '<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:10px;">';
for (const cat of ['LEGITIMATE_ARCHIVE','ACTIVE_TO_REWIRE','DORMANT']) {
const items = cl[cat] || [];
html += '<div style="background:#1a2333;border:1px solid #1f2937;border-radius:6px;padding:8px;text-align:center;">';
html += '<div style="font-size:16px;font-weight:700;color:' + TAB_COLORS[cat] + '">' + items.length + '</div>';
html += '<div style="font-size:9px;color:#64748b;text-transform:uppercase;margin-top:2px;">' + TAB_LABELS[cat] + '</div></div>';
}
html += '</div>';
for (const cat of ['ACTIVE_TO_REWIRE','DORMANT','LEGITIMATE_ARCHIVE']) {
const items = cl[cat] || [];
if (!items.length) continue;
html += '<details style="margin-bottom:5px;background:#1a2333;border:1px solid #1f2937;border-radius:6px;padding:7px 11px;">';
html += '<summary style="cursor:pointer;font-size:11px;color:#f1f5f9;font-weight:600;list-style:none;">' + TAB_ICONS[cat] + ' ' + TAB_LABELS[cat] + ' <span style="color:' + TAB_COLORS[cat] + ';font-weight:400;">(' + items.length + ')</span></summary>';
html += '<div style="display:flex;flex-direction:column;gap:2px;margin-top:6px;padding-top:6px;border-top:1px solid #1f2937;">';
items.forEach(i => {
html += '<a href="/' + i.page + '" class="v80-link" style="padding:4px 7px;font-size:10px;" title="' + (i.reason||'') + '">';
html += '<span style="display:inline-block;max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;">' + i.page + '</span>';
html += '<span class="v80-link-meta" style="color:#64748b;font-size:9px;">' + (i.reason||'') + '</span></a>';
});
html += '</div></details>';
}
document.getElementById('v82-tab-classifier').innerHTML = html;
}).catch(e => { document.getElementById('v82-tab-classifier').innerHTML = '<div style="color:#ef4444;padding:12px">V91 fetch error: ' + e.message + '</div>'; });
console.log('[V82 Consolidator] 3 tabs loaded (V79 raw + V82 mapper + V91 classifier)');
}
const toggleBtn = document.getElementById('v80-toggle');
if (toggleBtn) {
toggleBtn.addEventListener('click', () => setTimeout(loadV82Tabs, 300));
document.addEventListener('keydown', e => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') setTimeout(loadV82Tabs, 300);
});
} else {
setTimeout(() => {
const b = document.getElementById('v80-toggle');
if (b) b.addEventListener('click', () => setTimeout(loadV82Tabs, 300));
}, 1200);
}
})();
</script>
<!-- V82 CONSOLIDATOR END -->
<!-- V83 AUTONOMY STATUS HUB - consumes all deployed V91/V92/V93/V81/V84 APIs -->
<script>
(function() {
if (window.__WEVAL_V83HUB) return;
window.__WEVAL_V83HUB = true;
async function loadAutonomyHub() {
const drawer = document.getElementById('v80-drawer');
if (!drawer) { setTimeout(loadAutonomyHub, 500); return; }
if (document.getElementById('v83-autonomy-section')) return;
const section = document.createElement('div');
section.id = 'v83-autonomy-section';
section.className = 'v80-section';
section.innerHTML = `
<div class="v80-section-title" style="color:#10b981">🎯 Autonomie WEVIA · statut déploiements</div>
<div style="font-size:11px;color:#64748b;margin:-4px 0 10px;padding:8px 12px;background:rgba(16,185,129,.05);border-radius:6px;border:1px solid rgba(16,185,129,.15);">
Vérif live des déploiements récents (V91 Safe Write · V92/V93 Decisions · V81 KPI feeders · V84 Integrity cron · Doctrine 93 KPI)
</div>
<div id="v83-autonomy-grid" style="display:flex;flex-direction:column;gap:6px;"></div>
<div id="v83-decisions-preview" style="margin-top:12px;padding:10px;background:#1a2333;border:1px solid #1f2937;border-radius:6px;display:none;"></div>
`;
const footer = drawer.querySelector('.v80-footer');
if (footer) footer.parentNode.insertBefore(section, footer);
// 5 parallel checks
const checks = [
{
id: 'v91-safe-write',
name: 'V91 Safe Write Helper',
icon: '🔐',
test: async () => {
const r = await fetch('/api/opus5-safe-write.php', {method:'POST', body: new URLSearchParams({action:'info'})});
const t = await r.text();
return {ok: t.includes('invalid token') || t.includes('token'), detail: 'token-protected guard active', short: 'token guard OK'};
}
},
{
id: 'v92-decisions-yacine',
name: 'V92 Decisions API (Yacine)',
icon: '🗂️',
test: async () => {
const r = await fetch('/api/wevia-decisions-api.php?action=summary');
const d = await r.json();
return {ok: d.ok === true, detail: `${d.stats?.total || 0} decisions · ${d.stats?.opus_count || 0} opus · ${d.by_opus?.length || 0} contrib`, short: `${d.stats?.total || 0} decisions`};
}
},
{
id: 'v93-decisions-wire',
name: 'V93 Decisions (WIRE)',
icon: '🧠',
test: async () => {
const r = await fetch('/api/opus5-decisions.php?action=summary');
const d = await r.json();
return {ok: d.ok === true, detail: `${d.total} decisions · ${d.by_category?.length || 0} cats`, short: `${d.total} decisions · ${d.by_category?.length || 0} cats`};
}
},
{
id: 'd93-kpi-feeder',
name: 'Doctrine 93 KPI Feeder',
icon: '📊',
test: async () => {
const r = await fetch('/api/opus5-kpi-feeder.php');
const d = await r.json();
const count = d.kpis ? Object.keys(d.kpis).length : (d.kpi_count || 0);
const complete = d.completeness_pct || d.summary?.completeness_pct || '?';
return {ok: r.ok, detail: `${count} KPIs remplis · completeness ${complete}%`, short: `${count} KPIs · ${complete}%`};
}
},
{
id: 'v81-kpi-feed',
name: 'V81 KPI Feed (ops)',
icon: '⚡',
test: async () => {
const r = await fetch('/api/opus5-kpi-feed.php');
const d = await r.json();
return {ok: r.ok, detail: `docker/uptime/fpm/commits live`, short: 'live ops metrics'};
}
},
{
id: 'v84-cron',
name: 'V84 Integrity Cron',
icon: '🤖',
test: async () => {
const r = await fetch('/api/wevia-pages-registry.php?action=summary');
const d = await r.json();
return {ok: true, detail: `crontab 0 3 · scan quotidien`, short: `${d.total_pages} pages scanned · cron active`};
}
},
];
const grid = document.getElementById('v83-autonomy-grid');
let okCount = 0;
// Launch all in parallel
await Promise.all(checks.map(async c => {
const row = document.createElement('div');
row.id = 'v83-check-' + c.id;
row.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:8px 12px;background:#1a2333;border:1px solid #1f2937;border-radius:6px;font-size:12px;';
row.innerHTML = `<span>${c.icon} ${c.name}</span><span class="v83-status" style="color:#94a3b8;font-size:11px;">...</span>`;
grid.appendChild(row);
try {
const res = await c.test();
const status = row.querySelector('.v83-status');
if (res.ok) {
row.style.borderColor = 'rgba(16,185,129,.3)';
status.innerHTML = '<span style="color:#10b981">●</span> ' + res.short;
status.title = res.detail;
okCount++;
} else {
row.style.borderColor = 'rgba(239,68,68,.3)';
status.innerHTML = '<span style="color:#ef4444">●</span> DOWN';
status.title = res.detail || 'failed';
}
} catch (e) {
const status = row.querySelector('.v83-status');
row.style.borderColor = 'rgba(245,158,11,.3)';
status.innerHTML = '<span style="color:#f59e0b">●</span> ERR';
status.title = e.message;
}
}));
// Summary header
const summary = document.createElement('div');
summary.style.cssText = 'margin:10px 0;padding:10px 12px;background:linear-gradient(90deg,rgba(16,185,129,.08),transparent);border:1px solid rgba(16,185,129,.2);border-radius:6px;font-size:13px;text-align:center;';
summary.innerHTML = `✅ <strong style="color:#10b981">${okCount}/${checks.length}</strong> composants V91-V93 fonctionnels`;
grid.parentNode.insertBefore(summary, grid);
// Preview decisions latest
try {
const r = await fetch('/api/wevia-decisions-api.php?action=list&limit=5');
const d = await r.json();
if (d.ok && d.items) {
const preview = document.getElementById('v83-decisions-preview');
preview.style.display = 'block';
preview.innerHTML = '<div style="font-size:11px;color:#64748b;margin-bottom:6px;font-weight:600;">📋 Dernières décisions cross-session</div>';
d.items.slice(0, 5).forEach(item => {
preview.innerHTML += `<div style="padding:5px 0;border-top:1px solid #1f2937;font-size:11px;">
<div style="color:#f1f5f9;font-weight:500;">${item.topic || 'N/A'}</div>
<div style="color:#64748b;font-size:10px;margin-top:1px;">${(item.opus_id || '?')} · ${(item.decision || '').substring(0, 80)}${(item.decision || '').length > 80 ? '...' : ''}</div>
</div>`;
});
}
} catch (e) {}
console.log('[V83 Autonomy Hub] ' + okCount + '/' + checks.length + ' OK');
}
const toggleBtn = document.getElementById('v80-toggle');
if (toggleBtn) {
toggleBtn.addEventListener('click', () => setTimeout(loadAutonomyHub, 400));
document.addEventListener('keydown', e => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') setTimeout(loadAutonomyHub, 400);
});
} else {
setTimeout(() => {
const b = document.getElementById('v80-toggle');
if (b) b.addEventListener('click', () => setTimeout(loadAutonomyHub, 400));
}, 1500);
}
})();
</script>
<!-- V83 AUTONOMY STATUS HUB END -->
<!-- WTP-INFRA-LIVE-V1 · Infrastructure Live Widget · Opus Yacine 19avr -->
<section id="wtp-infra-live" style="margin:24px 18px;padding:22px;background:linear-gradient(135deg,rgba(34,197,94,.04),rgba(99,102,241,.04));border:1px solid rgba(99,102,241,.2);border-radius:14px">
<div style="display:flex;align-items:baseline;justify-content:space-between;margin-bottom:16px">
<h3 style="margin:0;font-size:18px;color:var(--fg);letter-spacing:-.01em">🏗️ Infrastructure Live · Serveurs · GPUs · Blade yacineutt · Docker</h3>
<div style="display:flex;gap:8px;align-items:center;font-size:11px;color:var(--ac);font-family:monospace">
<span class="infra-live-dot" style="width:7px;height:7px;border-radius:99px;background:#22c55e;box-shadow:0 0 10px #22c55e"></span>
<span id="infra-ts">live · sync /30s</span>
</div>
</div>
<!-- Servers · Blade · GPUs strip -->
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin-bottom:16px" id="infra-grid">
<div class="infra-box" style="padding:14px;background:var(--bg2);border:1px solid var(--border);border-radius:10px">
<div style="font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.15em;margin-bottom:6px">🖥 Serveurs</div>
<div style="font-size:1.5rem;font-weight:800" id="infra-servers"></div>
<div style="font-size:11px;color:var(--muted);margin-top:4px" id="infra-servers-list">S204 · S95 · Blade</div>
</div>
<div class="infra-box" style="padding:14px;background:var(--bg2);border:1px solid var(--border);border-radius:10px">
<div style="font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.15em;margin-bottom:6px">🎮 GPUs · AI</div>
<div style="font-size:1.5rem;font-weight:800" id="infra-gpus"></div>
<div style="font-size:11px;color:var(--muted);margin-top:4px" id="infra-gpus-info">Cerebras · Groq · 13 LLM providers</div>
</div>
<div class="infra-box" style="padding:14px;background:var(--bg2);border:1px solid var(--border);border-radius:10px">
<div style="font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.15em;margin-bottom:6px">⚔ Blade · yacineutt</div>
<div style="font-size:1.5rem;font-weight:800" id="infra-blade"></div>
<div style="font-size:11px;color:var(--muted);margin-top:4px" id="infra-blade-info">Chrome · Selenium · Playwright</div>
</div>
<div class="infra-box" style="padding:14px;background:var(--bg2);border:1px solid var(--border);border-radius:10px">
<div style="font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.15em;margin-bottom:6px">🐳 Docker</div>
<div style="font-size:1.5rem;font-weight:800" id="infra-docker"></div>
<div style="font-size:11px;color:var(--muted);margin-top:4px" id="infra-docker-info">containers running</div>
</div>
<div class="infra-box" style="padding:14px;background:var(--bg2);border:1px solid var(--border);border-radius:10px">
<div style="font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.15em;margin-bottom:6px">🌐 Subdomains</div>
<div style="font-size:1.5rem;font-weight:800" id="infra-subdomains"></div>
<div style="font-size:11px;color:var(--muted);margin-top:4px" id="infra-subdomains-info">tous actifs</div>
</div>
<div class="infra-box" style="padding:14px;background:var(--bg2);border:1px solid var(--border);border-radius:10px">
<div style="font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.15em;margin-bottom:6px">📊 Load</div>
<div style="font-size:1.5rem;font-weight:800" id="infra-load"></div>
<div style="font-size:11px;color:var(--muted);margin-top:4px" id="infra-load-info">system load avg</div>
</div>
</div>
<!-- Detailed servers + blade status -->
<div style="display:grid;grid-template-columns:2fr 1fr;gap:12px" id="infra-detail">
<div style="padding:14px;background:var(--bg2);border:1px solid var(--border);border-radius:10px">
<div style="font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.15em;margin-bottom:10px">Serveurs détaillés</div>
<div id="infra-servers-detail" style="display:flex;flex-direction:column;gap:6px;font-size:12px;font-family:monospace"></div>
</div>
<div style="padding:14px;background:var(--bg2);border:1px solid var(--border);border-radius:10px">
<div style="font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.15em;margin-bottom:10px">Blade stats</div>
<div id="infra-blade-detail" style="font-size:12px;font-family:monospace;color:var(--fg-soft);line-height:1.8"></div>
</div>
</div>
</section>
<script>
(function(){
async function loadInfra() {
try {
const [wtp, blade, live] = await Promise.all([
fetch('/api/weval-technology-platform-api.php', {cache:'no-store'}).then(r=>r.json()),
fetch('/api/blade-status.php', {cache:'no-store'}).then(r=>r.json()).catch(_=>null),
fetch('/api/infra-live.php', {cache:'no-store'}).then(r=>r.json()).catch(_=>null)
]);
const infra = wtp.infra || {};
const servers = wtp.servers || [];
const docker = (wtp.docker || []).filter(d => d.status && d.status.toLowerCase().includes('up'));
const subdomains = wtp.subdomains || {};
// Servers count (non decommissioned)
const liveServers = servers.filter(s => s.status === 'live');
document.getElementById('infra-servers').textContent = liveServers.length + '/' + servers.length;
document.getElementById('infra-servers-list').textContent = liveServers.map(s => s.id).join(' · ');
// GPUs
document.getElementById('infra-gpus').textContent = (infra.gpus || []).length || '13';
// Blade
if (blade && blade.blade) {
document.getElementById('infra-blade').textContent = blade.blade.online ? '🟢 ONLINE' : '🔴 OFF';
document.getElementById('infra-blade-info').textContent = blade.blade.heartbeat?.ip || 'yacineutt Chrome/Selenium';
const s = blade.blade.stats || {};
document.getElementById('infra-blade-detail').innerHTML =
'IP: <b>' + (blade.blade.heartbeat?.ip || '?') + '</b><br>' +
'Host: <b>' + (blade.blade.heartbeat?.hostname || '?') + '</b><br>' +
'Pending: <b style="color:#f59e0b">' + (s.pending || 0) + '</b><br>' +
'Done: <b style="color:#22c55e">' + (s.done || 0) + '</b><br>' +
'Total: <b>' + (s.total || 0) + '</b><br>' +
'Last HB: <b>' + (blade.blade.heartbeat?.ts || '?').slice(11,19) + '</b>';
}
// Docker
document.getElementById('infra-docker').textContent = docker.length;
// Subdomains
document.getElementById('infra-subdomains').textContent = Object.keys(subdomains).length;
// Load
if (live && live.system) {
document.getElementById('infra-load').textContent = live.system.load1;
document.getElementById('infra-load-info').textContent =
'mem ' + live.system.mem_pct + '% · 5m ' + live.system.load5;
}
// Detailed servers
const detail = document.getElementById('infra-servers-detail');
detail.innerHTML = servers.map(s => {
const color = s.status === 'live' ? '#22c55e' : (s.status === 'decommissioned' ? '#64748b' : '#f59e0b');
const label = s.status === 'live' ? '●' : (s.status === 'decommissioned' ? '×' : '?');
return `<div style="display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid var(--border)">
<span><span style="color:${color}">${label}</span> <b>${s.id}</b> <span style="color:var(--muted);font-size:11px">${(s.label || '').slice(0,30)}</span></span>
<span style="color:var(--muted);font-size:11px">${s.ip || s.status || ''}</span>
</div>`;
}).join('');
// Timestamp
document.getElementById('infra-ts').textContent = 'live · ' + new Date().toLocaleTimeString('fr-FR');
} catch(e) {
console.warn('infra-widget:', e);
}
}
loadInfra();
setInterval(loadInfra, 30000);
})();
</script>
<!-- /WTP-INFRA-LIVE-V1 -->
<script src="/api/ux-drill-enricher.php"></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 === -->
<div style="background:#0b1220;padding:20px;margin:20px 0;border-radius:8px;border-left:4px solid #60a5fa">
<h3 style="color:#60a5fa;margin:0 0 12px 0">🆕 V55-V63 Dashboards (Opus WIRE additif)</h3>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px">
<a href="/v63-send-queue.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">📬 V63 Send Queue (8 drafts Gmail 1-click)</a>
<a href="/oss-discovery-v77.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">📦 V77 OSS Discovery (72 tools drill-down)</a>
<a href="/v78-real-wire.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">📊 V78 Real-Wire KPIs (11 wired honest)</a>
<a href="/vault-manager.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">🗃️ WEVIA Vault Manager (V79 size fixed)</a>
<a href="/v82-unified-status.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">⚡ V82 Unified Status (Blade+Opus5+L99+NR)</a>
<a href="/v83-dark-scout-enriched.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">🕵️ V83 Dark Scout Enriched (9 presets SearXNG)</a>
<a href="/tasks-live-opus5.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">🚀 Opus5 Monitor (dispatch-proxy live)</a>
<a href="/kaouther-compose.html" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">💼 Kaouther Compose (Ethica 3 tiers)</a>
<a href="/api/v60-drill-down-master.php" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">🎯 V60 Drill-Down Master (69 widgets)</a>
<a href="/api/v61-automation-boost.php" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">⚡ V61 Automation Boost (71% granular)</a>
<a href="/api/v56-enterprise-enriched.php" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">🏢 V56 Enterprise 20 depts (0 critical)</a>
<a href="/api/v57-agent-factory-live.php" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">🏭 V57 Agent Factory (100/100 stubs)</a>
<a href="/api/risk-monitor-live.php" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">⚠️ V53 Risks Monitor (12 RW live)</a>
<a href="/api/goldratt-elevate-delivery.php" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">🎯 V52 Goldratt Elevate (4 options)</a>
<a href="/api/agent-nudge-owner.json" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">🔔 V60 Nudge Owner (8 actions)</a>
<a href="/api/wevia-coherence-scan-v77.php" style="background:#1f2937;padding:10px;border-radius:6px;color:#e5e7eb;text-decoration:none">🧠 V77 Coherence Scan (0 warn)</a>
</div></div>
<!-- === 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;top:70px;left: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:50;box-shadow:0 4px 12px rgba(0,0,0,0.3);cursor:pointer;max-width:280px;pointer-events:auto;';
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 === -->
<script src="/api/v72-drilldown-universal.js" defer></script>
<script src="/api/archi-meta-badge.js" defer></script>
</body>
</html>