Files
html/architecture.html
2026-04-19 15:20:03 +02:00

578 lines
57 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>WEVAL — Architecture Control</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
<style>@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@600;700;800;900&display=swap');
:root{
--bg:#09090b;--s1:#18181b;--s2:#27272a;--s3:#3f3f46;
--card:#18181b;--card-h:#1e1e22;
--border:rgba(255,255,255,.08);--border-h:rgba(255,255,255,.16);
--acc:#3b82f6;--g:#22c55e;--r:#ef4444;--o:#eab308;--p:#a78bfa;--bl:#60a5fa;--y:#fbbf24;
--t:#fafafa;--dim:#a1a1aa;--dim2:#71717a;--dim3:#52525b;
--glow:0 0 15px rgba(59,130,246,.15),0 0 30px rgba(59,130,246,.08);
--glow-g:0 0 15px rgba(34,197,94,.15),0 0 30px rgba(34,197,94,.08);
--glow-r:0 0 15px rgba(239,68,68,.15),0 0 30px rgba(239,68,68,.08);
--radius:12px;
--hf:'JetBrains Mono',monospace;--bf:'Inter',system-ui,sans-serif}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:var(--bf);background:var(--bg);color:var(--t);min-height:100vh;overflow-x:hidden;
background-image:none}
::selection{background:rgba(0,229,255,.2);color:#fff}
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:var(--bg)}::-webkit-scrollbar-thumb{background:var(--dim2);border-radius:3px}
/* ANIMATIONS */
@keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
@keyframes glow-pulse{0%,100%{box-shadow:var(--glow)}50%{box-shadow:0 0 40px rgba(0,229,255,.2)}}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes dash{to{stroke-dashoffset:0}}
.fade{animation:fadeUp .5s ease both}
.d1{animation-delay:.05s}.d2{animation-delay:.1s}.d3{animation-delay:.15s}.d4{animation-delay:.2s}.d5{animation-delay:.25s}
.wrap{max-width:1480px;margin:0 auto;padding:16px 24px}
/* HEADER */
.hdr{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid var(--border)}
.hdr-left{display:flex;align-items:center;gap:14px}
.logo{font-family:var(--bf);font-size:1.2rem;font-weight:700;color:var(--t);letter-spacing:-.04em}
.logo b{color:var(--acc)}
.hdr-badge{padding:3px 10px;border-radius:20px;background:rgba(0,255,136,.08);border:1px solid rgba(0,255,136,.15);font-size:.62rem;font-family:var(--hf);color:var(--g);text-transform:uppercase;letter-spacing:1px}
.hdr-right{text-align:right}
.live-dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--g);box-shadow:var(--glow-g);animation:pulse 2s infinite;margin-right:5px}
.hdr-ts{font-family:var(--hf);font-size:.7rem;color:var(--dim)}
/* SEARCH */
.search-wrap{position:relative;margin:14px 0}
.search-input{width:100%;padding:12px 18px 12px 42px;border-radius:12px;border:1px solid var(--border);background:var(--card);backdrop-filter:blur(12px);color:var(--t);font-size:.85rem;font-family:var(--bf);outline:none;transition:all .25s}
.search-input:focus{border-color:var(--acc);box-shadow:var(--glow)}
.search-icon{position:absolute;left:14px;top:50%;transform:translateY(-50%);color:var(--dim);font-size:1.1rem}
.search-results{position:absolute;top:100%;left:0;right:0;background:var(--card);backdrop-filter:blur(16px);border:1px solid var(--border);border-top:none;border-radius:0 0 12px 12px;max-height:280px;overflow-y:auto;z-index:100;display:none}
.search-results.open{display:block}
.sr-item{padding:10px 16px;border-bottom:1px solid rgba(0,229,255,.04);cursor:pointer;font-size:.8rem;display:flex;justify-content:space-between;align-items:center;transition:background .15s}
.sr-item:hover{background:rgba(0,229,255,.06)}
/* ALERTS */
.alerts{display:flex;gap:8px;overflow-x:auto;padding:12px 0;margin-bottom:4px}
.alert{display:flex;align-items:center;gap:5px;padding:5px 14px;border-radius:20px;font-size:.68rem;font-weight:500;white-space:nowrap;cursor:pointer;transition:all .2s;backdrop-filter:blur(8px)}
.alert:hover{transform:scale(1.04)}
.alert-crit{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.15);color:var(--r)}
.alert-warn{background:rgba(255,159,67,.08);border:1px solid rgba(255,159,67,.15);color:var(--o)}
.alert-ok{background:rgba(0,255,136,.06);border:1px solid rgba(0,255,136,.12);color:var(--g)}
.alert-info{background:rgba(0,229,255,.05);border:1px solid rgba(0,229,255,.1);color:var(--acc)}
/* TABS */
.tabs{display:flex;gap:2px;border-bottom:1px solid var(--border);overflow-x:auto;padding-bottom:0;margin:0}
.tab{padding:10px 18px;font-size:.8rem;color:var(--dim2);cursor:pointer;border:none;background:none;font-family:var(--bf);font-weight:500;letter-spacing:-.01em;border-bottom:2px solid transparent;transition:all .2s;white-space:nowrap}
.tab.on{color:var(--acc);border-bottom-color:var(--acc)}
.tab:hover{color:var(--t)}
.pn{display:none}.pn.on{display:block;animation:fadeUp .4s ease}
/* GRID */
.g{display:grid;gap:12px;margin:14px 0}
.g2{grid-template-columns:1fr 1fr}.g3{grid-template-columns:1fr 1fr 1fr}.g4{grid-template-columns:repeat(4,1fr)}.g5{grid-template-columns:repeat(5,1fr)}
@media(max-width:1100px){.g4,.g5{grid-template-columns:repeat(2,1fr)}}
@media(max-width:700px){.g2,.g3,.g4,.g5{grid-template-columns:1fr}}
/* CARD */
.cd{background:var(--card);backdrop-filter:blur(12px);border:1px solid var(--border);border-radius:var(--radius);padding:16px;transition:all .25s}
.cd:hover{border-color:var(--border-h);box-shadow:var(--glow)}
.cd.click{cursor:pointer}
.cd.click:hover{transform:translateY(-2px)}
.cd h3{font-family:var(--hf);font-size:.62rem;color:var(--dim);text-transform:uppercase;letter-spacing:1.5px;margin-bottom:8px}
/* KPI */
.kpi{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:20px 12px;min-height:100px}
.kpi-val{font-family:var(--hf);font-size:1.9rem;font-weight:700;line-height:1;margin-bottom:6px;letter-spacing:-1px}
.kpi-label{font-size:.72rem;color:var(--dim);font-weight:400;margin-top:2px}
.kpi-sub{font-size:.62rem;color:var(--dim2)}
/* TAG */
.tg{display:inline-block;padding:2px 8px;border-radius:10px;font-size:.58rem;font-weight:600;font-family:var(--hf);letter-spacing:.3px}
.tg-g{background:rgba(0,255,136,.1);color:var(--g);border:1px solid rgba(0,255,136,.15)}
.tg-r{background:rgba(255,71,87,.1);color:var(--r);border:1px solid rgba(255,71,87,.15)}
.tg-o{background:rgba(255,159,67,.08);color:var(--o);border:1px solid rgba(255,159,67,.12)}
.tg-b{background:rgba(96,165,250,.1);color:var(--bl);border:1px solid rgba(96,165,250,.15)}
.tg-p{background:rgba(167,139,250,.1);color:var(--p);border:1px solid rgba(167,139,250,.15)}
.tg-c{background:rgba(0,229,255,.06);color:var(--acc);border:1px solid rgba(0,229,255,.1)}
.tg-y{background:rgba(251,191,36,.08);color:var(--y);border:1px solid rgba(251,191,36,.12)}
.tg-d{background:rgba(45,55,72,.4);color:var(--dim);border:1px solid rgba(45,55,72,.6)}
/* TABLE */
table{width:100%;border-collapse:collapse;font-size:.76rem}
th{text-align:left;padding:8px 10px;font-size:.7rem;font-weight:600;letter-spacing:.03em;color:var(--dim2);border-bottom:1px solid var(--border)}
td{padding:6px 10px;border-bottom:1px solid rgba(0,229,255,.03)}
tr:hover td{background:rgba(0,229,255,.02)}
/* BAR */
.bar{height:3px;border-radius:2px;background:var(--dim2);overflow:hidden}.bar-f{height:100%;border-radius:2px;transition:width .8s ease}
/* SCORE RING */
.ring{position:relative;margin:0 auto}.ring svg{display:block}
.ring svg{transform:rotate(-90deg)}
.ring .rv{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;text-align:center}
.ring-n{font-family:var(--hf);font-weight:700;line-height:1}
/* WM PANEL */
.wm{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:16px 0;align-items:start}
.wm-hd{font-family:var(--hf);font-size:.6rem;color:var(--acc);text-transform:uppercase;letter-spacing:2px;margin-bottom:10px;display:flex;align-items:center;gap:6px}
.wm-hd::before{content:'';width:3px;height:12px;background:var(--acc);border-radius:2px}
.wm-row{display:flex;align-items:center;gap:8px;padding:7px 0;border-bottom:1px solid rgba(0,229,255,.04);font-size:.78rem}
.wm-row:last-child{border:none}
.wm-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;animation:pulse 3s infinite}
.wm-label{flex:1;color:var(--t);font-weight:400}
.wm-ts{font-family:var(--hf);font-size:.62rem;color:var(--dim)}
.wm-gap{padding:8px 0;border-bottom:1px solid rgba(0,229,255,.04)}
.wm-gap:last-child{border:none}
.wm-gap-t{font-weight:600;font-size:.78rem;color:var(--t);margin-bottom:3px}
.wm-gap-d{font-size:.68rem;color:var(--dim);line-height:1.4}
.wm-tl{position:relative;padding-left:18px}
.wm-tl-i{position:relative;padding:5px 0 5px 12px;border-left:1px solid var(--dim2);font-size:.72rem}
.wm-tl-i::before{content:'';position:absolute;left:-4px;top:9px;width:7px;height:7px;border-radius:50%;background:var(--acc);box-shadow:0 0 8px rgba(0,229,255,.4)}
.wm-tl-t{color:var(--t);font-weight:500}.wm-tl-d{font-family:var(--hf);font-size:.58rem;color:var(--dim);margin-top:1px}
.wm-btn{width:100%;padding:10px;background:linear-gradient(135deg,rgba(0,229,255,.15),rgba(0,229,255,.05));border:1px solid rgba(0,229,255,.2);border-radius:10px;color:var(--acc);font-family:var(--hf);font-size:.72rem;font-weight:700;cursor:pointer;transition:all .25s;letter-spacing:.5px;text-transform:uppercase}
.wm-btn:hover{background:linear-gradient(135deg,rgba(0,229,255,.25),rgba(0,229,255,.1));box-shadow:var(--glow);transform:translateY(-1px)}
.wm-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}
/* BPMN */
.bpmn-card{border-left:3px solid var(--g);margin-bottom:8px}
.bpmn-card.standby{border-left-color:var(--o)}
.bpmn-hd{display:flex;justify-content:space-between;align-items:center;cursor:pointer;padding:4px 0}
.bpmn-hd:hover .bpmn-nm{color:var(--acc)}
.bpmn-nm{font-weight:700;color:var(--t);font-size:.88rem;transition:color .15s}
.bpmn-flow{display:flex;flex-wrap:wrap;gap:3px;align-items:center;margin-top:8px}
.bpmn-step{display:flex;align-items:center;gap:4px;padding:5px 10px;border-radius:8px;background:rgba(0,229,255,.03);border:1px solid var(--border);font-size:.68rem;cursor:pointer;transition:all .2s}
.bpmn-step:hover{border-color:var(--acc);background:rgba(0,229,255,.06);box-shadow:var(--glow)}
.bpmn-arr{color:var(--dim);font-size:.6rem}
/* SOA */
.soa{display:inline-flex;align-items:center;gap:5px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;font-size:.72rem;cursor:pointer;transition:all .2s}
.soa:hover{border-color:var(--acc);box-shadow:var(--glow)}
/* PIPE */
.pipe{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:10px;border:1px solid var(--border);background:var(--card);margin-bottom:5px;cursor:pointer;transition:all .2s}
.pipe:hover{border-color:var(--acc);box-shadow:var(--glow)}
.pipe-dot{width:6px;height:6px;border-radius:50%;background:var(--g);box-shadow:var(--glow-g);animation:pulse 3s infinite}
.pipe-nm{font-weight:600;font-size:.8rem;color:var(--t);min-width:140px}
.pipe-desc{font-size:.68rem;color:var(--dim);flex:1}
.pipe-freq{font-family:var(--hf);font-size:.62rem;color:var(--acc);background:rgba(0,229,255,.06);padding:2px 8px;border-radius:6px;border:1px solid rgba(0,229,255,.1)}
/* RECO */
.reco{display:flex;gap:10px;padding:10px 12px;border-radius:10px;border:1px solid var(--border);background:var(--card);margin-bottom:6px;cursor:pointer;transition:all .2s}
.reco:hover{border-color:var(--border-h);box-shadow:var(--glow)}
.reco-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;margin-top:5px}
.reco-t{font-weight:600;font-size:.8rem;color:var(--t)}.reco-d{font-size:.68rem;color:var(--dim);margin-top:2px;line-height:1.4}
/* MODAL */
.modal-bg{position:fixed;inset:0;background:rgba(3,5,8,.9);backdrop-filter:blur(8px);z-index:200;display:none;align-items:center;justify-content:center}
.modal-bg.open{display:flex}
.modal{background:var(--card-h);backdrop-filter:blur(16px);border:1px solid var(--border-h);border-radius:16px;padding:24px;max-width:650px;width:92%;max-height:80vh;overflow-y:auto;animation:fadeUp .3s ease}
.modal-x{float:right;background:none;border:none;color:var(--dim);font-size:1.2rem;cursor:pointer;padding:4px 8px;border-radius:6px;transition:all .15s}
.modal-x:hover{color:var(--r);background:rgba(255,71,87,.1)}
.modal h2{font-family:var(--hf);font-size:.9rem;color:var(--acc);margin-bottom:12px}
/* AGENT */
.agt{display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:10px;border:1px solid var(--border);background:var(--card);cursor:pointer;transition:all .2s}
.agt:hover{border-color:var(--acc);box-shadow:var(--glow)}
.agt-icon{width:30px;height:30px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-family:var(--hf);font-size:.68rem;font-weight:700;flex-shrink:0}
.decision{padding:8px 12px;border-left:3px solid var(--p);background:var(--card);margin-bottom:5px;border-radius:0 8px 8px 0;font-size:.72rem;line-height:1.5}
.commit{display:flex;gap:8px;padding:5px 0;border-bottom:1px solid rgba(0,229,255,.03);font-size:.72rem;align-items:center}
.commit-h{font-family:var(--hf);color:var(--acc);font-size:.62rem;flex-shrink:0}
.commit-m{color:var(--t);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
section{margin:14px 0}section>h2{font-family:var(--bf);font-size:.82rem;font-weight:600;color:var(--t);letter-spacing:-.02em;margin-bottom:10px;padding-bottom:6px;border-bottom:1px solid var(--border)}
h2 .cnt{font-size:.65rem;color:var(--acc);margin-left:6px;font-weight:400;letter-spacing:0}
/* PREMIUM OVERRIDES */
.kpi{position:relative;overflow:hidden;transition:all .3s}
.kpi::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 10%,rgba(255,255,255,.06) 50%,transparent 90%);pointer-events:none}
.kpi:hover{transform:translateY(-2px);box-shadow:0 4px 24px rgba(0,0,0,.3),var(--glow)}
.kpi-val{font-size:2rem!important;font-variant-numeric:tabular-nums lining-nums;letter-spacing:-.04em;color:var(--t)!important}
.kpi-label{font-size:.78rem!important;font-weight:500;margin-top:6px!important;color:var(--t)!important}
.kpi-sub{margin-top:4px!important}
/* Premium cards */
.cd{position:relative;overflow:hidden}
.cd::after{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 20%,rgba(255,255,255,.04) 50%,transparent 80%);pointer-events:none}
/* WM section headers */
.wm-hd{font-size:.65rem!important;padding-bottom:8px;border-bottom:1px solid rgba(0,229,255,.06);margin-bottom:12px!important}
.wm-hd::before{width:3px;height:14px;border-radius:2px;box-shadow:0 0 8px var(--acc)}
/* Premium rows */
.wm-row{padding:8px 4px!important;transition:background .15s;border-radius:6px;margin:0 -4px}
.wm-row:hover{background:rgba(0,229,255,.03)}
.wm-dot{width:8px!important;height:8px!important;box-shadow:0 0 6px currentColor}
/* Gap items */
.wm-gap{padding:10px 0!important;transition:background .15s;border-radius:6px}
.wm-gap:hover{background:rgba(0,229,255,.02)}
.wm-gap-t{font-size:.82rem!important;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.wm-gap-d{font-size:.7rem!important;margin-top:4px!important;padding-left:0}
/* Timeline premium */
.wm-tl-i{padding:8px 0 8px 16px!important}
.wm-tl-i::before{width:8px!important;height:8px!important;box-shadow:0 0 12px rgba(0,229,255,.5)!important}
.wm-tl-t{font-size:.76rem!important}
.wm-tl-d{font-size:.6rem!important;margin-top:2px!important}
/* Trigger button premium */
.wm-btn{padding:12px!important;font-size:.76rem!important;border-radius:10px!important;transition:all .3s!important}
.wm-btn:hover{box-shadow:0 4px 24px rgba(0,229,255,.2)!important;transform:translateY(-2px)!important}
/* Overview 3-col cards */
.ov-top{display:grid;grid-template-columns:280px 1fr 240px;gap:14px;margin:14px 0;align-items:stretch}
.ov-score{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:20px}
.ov-cx{padding:16px}
.ov-sys{padding:16px}
.ov-sys-row{display:flex;justify-content:space-between;align-items:center;padding:6px 4px;border-bottom:1px solid rgba(0,229,255,.04);transition:background .15s;border-radius:4px;margin:0 -4px}
.ov-sys-row:hover{background:rgba(0,229,255,.03)}
.ov-sys-row:last-child{border:none}
.ov-sys-label{font-size:.8rem;color:var(--t)}
.ov-sys-val{font-family:var(--hf);font-weight:700;font-size:.9rem}
/* CORTEX mini cards */
.cx-card{background:var(--s2);border:1px solid var(--border);border-radius:var(--radius);padding:16px 10px;text-align:center;transition:all .25s}
.cx-card:hover{border-color:var(--border-h);box-shadow:0 2px 16px rgba(0,0,0,.3);transform:translateY(-1px)}
.cx-val{font-family:var(--hf);font-size:1.5rem;font-weight:700;line-height:1;margin-bottom:6px;font-variant-numeric:tabular-nums}
.cx-label{font-size:.58rem;color:var(--dim);text-transform:uppercase;letter-spacing:1px}
footer{text-align:center;padding:20px;color:var(--dim);font-size:.58rem;font-family:var(--hf);letter-spacing:1px;border-top:1px solid var(--border);margin-top:24px}
#ld{position:fixed;inset:0;background:var(--bg);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:999;gap:12px}
#ld .spin{width:32px;height:32px;border:2px solid var(--dim2);border-top-color:var(--acc);border-radius:50%;animation:spin .8s linear infinite}
</style>
</head>
<body style="padding-top:60px"><div style="position:fixed;top:0;left:0;right:0;height:28px;background:#ffffffee;z-index:100;display:flex;align-items:center;padding:0 14px;font-family:Nunito,sans-serif;font-size:.65rem;gap:12px;border-bottom:1px solid #e2e8f0;backdrop-filter:blur(8px)"><b style="color:#059669">WEVIA</b></div>
<div style="position:fixed;top:30px;left:0;right:0;display:flex;justify-content:center;gap:5px;padding:4px;z-index:100;background:#f8fafcee;backdrop-filter:blur(8px);font-family:Nunito,sans-serif">
<a href="/agents-archi.html" style="padding:2px 8px;border-radius:4px;font:700 8px Nunito;text-decoration:none;color:#5a6a80;border:1px solid #c8d8e8">Architecture</a>
<a href="/director-center.html" style="padding:2px 8px;border-radius:4px;font:700 8px Nunito;text-decoration:none;color:#5a6a80;border:1px solid #c8d8e8">Director</a>
<a href="/wevia-meeting-rooms.html" style="padding:2px 8px;border-radius:4px;font:700 8px Nunito;text-decoration:none;color:#5a6a80;border:1px solid #c8d8e8">Rooms</a>
<a href="/enterprise-model.html" style="padding:2px 8px;border-radius:4px;font:700 8px Nunito;text-decoration:none;color:#5a6a80;border:1px solid #c8d8e8">Enterprise</a>
<a href="/value-stream.html" style="padding:2px 8px;border-radius:4px;font:700 8px Nunito;text-decoration:none;color:#5a6a80;border:1px solid #c8d8e8">VSM</a>
<a href="/value-chain.html" style="padding:2px 8px;border-radius:4px;font:700 8px Nunito;text-decoration:none;color:#5a6a80;border:1px solid #c8d8e8">Chain</a>
<a href="/toolhub.html" style="padding:2px 8px;border-radius:4px;font:700 8px Nunito;text-decoration:none;color:#5a6a80;border:1px solid #c8d8e8">Tools</a>
<a href="/wiki.html" style="padding:2px 8px;border-radius:4px;font:700 8px Nunito;text-decoration:none;color:#5a6a80;border:1px solid #c8d8e8">Wiki</a>
<a href="/agents-ia.html" style="padding:2px 8px;border-radius:4px;font:700 8px Nunito;text-decoration:none;color:#5a6a80;border:1px solid #c8d8e8">Pyramid</a>
<a href="/director-chat.html" style="padding:2px 8px;border-radius:4px;font:700 8px Nunito;text-decoration:none;color:#5a6a80;border:1px solid #c8d8e8">Chat</a>
<a href="/l99-brain.html" style="padding:2px 8px;border-radius:4px;font:700 8px Nunito;text-decoration:none;color:#5a6a80;border:1px solid #c8d8e8">L99</a>
</div>
<div id="ld"><div class="spin"></div><div style="font-family:var(--hf);font-size:.72rem;color:var(--acc);letter-spacing:2px">SCANNING INFRASTRUCTURE</div></div>
<div class="wrap" id="app" style="display:none">
<div class="hdr fade"><div class="hdr-left"><div class="logo">WEVAL <b>ARCHITECTURE</b></div><div class="hdr-badge">live</div></div><div class="hdr-right"><div class="hdr-ts"><span class="live-dot"></span><span id="ts"></span></div><div class="hdr-ts" id="st"></div></div></div>
<div class="search-wrap fade d1"><span class="search-icon"></span><input class="search-input" id="gs" placeholder="Search services, APIs, processes, agents, domains..." oninput="gSearch(this.value)" onfocus="this.parentNode.querySelector('.search-results').classList.add('open')" onblur="setTimeout(()=>document.querySelector('.search-results').classList.remove('open'),200)"><div class="search-results" id="gsr"></div></div>
<div class="alerts fade d2" id="alerts"></div>
<div class="g g5 fade d3" id="kpi"></div>
<div class="tabs fade d4" id="tabs"></div>
<div id="pn"></div>
<footer>WEVAL CONSULTING · ARCHITECTURE RÉFÉRENTIEL · AUTONOMOUS ENGINE · WEVIA MASTER</footer>
</div>
<div class="modal-bg" id="modal" onclick="if(event.target===this)xModal()"><div class="modal"><button class="modal-x" onclick="xModal()"></button><div id="mc"></div></div></div>
<script>
let D=null,T=null,ct='overview',cf='all',sq='';
const TB=[{id:'overview',l:'Overview'},{id:'reco',l:'Health'},{id:'cortex',l:'CORTEX'},{id:'pipes',l:'Pipelines'},{id:'apps',l:'Apps'},{id:'infra',l:'Infra'},{id:'ai',l:'AI Stack'},{id:'data',l:'Data'},{id:'bpmn',l:'BPMN'},{id:'soa',l:'SOA'},{id:'topo',l:'Topology'},{id:'log',l:'Changelog'}];
const tg=(t,c)=>`<span class="tg tg-${c}">${t}</span>`;
const kp=(v,l,s,c)=>`<div class="cd click kpi fade" onclick="drill('${l}')"><div class="kpi-val" style="color:var(--${c})">${v}</div><div class="kpi-label">${l}</div><div class="kpi-sub">${s}</div></div>`;
function oModal(h){document.getElementById('mc').innerHTML=h;document.getElementById('modal').classList.add('open')}
function xModal(){document.getElementById('modal').classList.remove('open')}
function drill(l){let h=`<h2>${l}</h2>`;
if(l==='Docker'){h+='<table><thead><tr><th>Container</th><th>Status</th></tr></thead><tbody>';D.docker.forEach(c=>{const u=c.status.includes('Up');h+=`<tr><td style="font-weight:600">${c.name}</td><td>${tg(u?'UP':'DOWN',u?'g':'r')}</td></tr>`});h+='</tbody></table>'}
else if(l==='APIs'){h+=`<p style="margin:8px 0">S204: ${D.screens.s204_api_php} PHP endpoints</p><p>S95 Arsenal: ${D.screens.s95_arsenal_api} APIs</p>`}
else if(l.includes('cran')){h+=`<p style="margin:8px 0">S204 HTML: ${D.screens.s204_html}</p><p>S95 Arsenal HTML: ${D.screens.s95_arsenal_html}</p>`}
else{const r=D.recommendations||{};(r.recommendations||[]).forEach(rc=>{h+=recoH(rc)})}
oModal(h)}
function gSearch(q){if(!q||q.length<2){document.getElementById('gsr').innerHTML='';return}const ql=q.toLowerCase();let r=[];
D.applications.forEach(a=>{if(JSON.stringify(a).toLowerCase().includes(ql))r.push({l:a.name,t:'App',tab:'apps'})});
D.docker.forEach(c=>{if(c.name.toLowerCase().includes(ql))r.push({l:c.name,t:'Docker',tab:'infra'})});
if(T?.bpmn_processes)T.bpmn_processes.forEach(p=>{if(JSON.stringify(p).toLowerCase().includes(ql))r.push({l:p.name,t:'BPMN',tab:'bpmn'})});
if(T?.soa_services)T.soa_services.forEach(s=>{if(s.name.toLowerCase().includes(ql))r.push({l:s.name,t:'SOA',tab:'soa'})});
D.ai_providers.forEach(p=>{if(JSON.stringify(p).toLowerCase().includes(ql))r.push({l:p.name,t:'AI',tab:'ai'})});
document.getElementById('gsr').innerHTML=r.slice(0,8).map(i=>`<div class="sr-item" onclick="sw('${i.tab}');document.querySelector('.search-results').classList.remove('open')"><span>${i.l}</span>${tg(i.t,'c')}</div>`).join('')||'<div class="sr-item" style="color:var(--dim)">No results</div>'}
async function load(){try{const[r1,r2]=await Promise.all([fetch('/api/architecture-index.json?t='+Date.now()),fetch('/api/architecture-topology.json?t='+Date.now())]);D=await r1.json();T=await r2.json();render();document.getElementById('ld').style.display='none';document.getElementById('app').style.display='block'}catch(e){document.getElementById('ld').innerHTML='<div style="color:var(--r)">'+e.message+'</div>'}}
function sw(id){ct=id;document.querySelectorAll('.tab').forEach(t=>t.classList.toggle('on',t.dataset.id===id));document.querySelectorAll('.pn').forEach(p=>p.classList.toggle('on',p.id==='p-'+id))}
function render(){
document.getElementById('ts').textContent=D.generated;document.getElementById('st').textContent=D.scan_time_ms+'ms';
const tv=D.qdrant.reduce((s,q)=>s+q.vectors,0),rec=D.recommendations||{},recs=rec.recommendations||[];
const crits=recs.filter(r=>r.severity==='critical'),warns=recs.filter(r=>r.severity==='warning');
let al='';crits.forEach(r=>{al+=`<div class="alert alert-crit" onclick="sw('reco')">⚠ ${r.title}</div>`});
warns.forEach(r=>{al+=`<div class="alert alert-warn" onclick="sw('reco')">${r.title}</div>`});
al+=`<div class="alert alert-ok">L99: ${D.l99?.auth?.pass||0}/${(D.l99?.auth?.pass||0)+(D.l99?.auth?.fail||0)} pass</div>`;
al+=`<div class="alert alert-info">BPMN: ${T?.bpmn_processes?.length||0} proc</div>`;
al+=`<div class="alert alert-info">SOA: ${T?.soa_stats?.total_services||0} svc</div>`;
const ux=D.ux_agent||{};
al+=`<div class="alert ${ux.fail?'alert-crit':'alert-ok'}" onclick="sw('overview')">UX: ${ux.pass||0}/${ux.total||0} pass</div>`;
document.getElementById('alerts').innerHTML=al;
document.getElementById('kpi').innerHTML=[kp(D.docker.length,'Docker','containers','g'),kp(D.screens.s204_api_php+D.screens.s95_arsenal_api,'APIs','endpoints','bl'),kp(D.screens.s204_html+D.screens.s95_arsenal_html,'Écrans','pages','p'),kp(tv.toLocaleString(),'Vecteurs','Qdrant','o'),kp(rec.score+'/100','Health','score','acc')].join('');
document.getElementById('tabs').innerHTML=TB.map(t=>`<button class="tab${t.id===ct?' on':''}" data-id="${t.id}" onclick="sw('${t.id}')">${t.l}</button>`).join('');
document.getElementById('pn').innerHTML=TB.map(t=>`<div class="pn${t.id===ct?' on':''}" id="p-${t.id}">${rp(t.id)}</div>`).join('')}
function rp(id){switch(id){case'overview':return rpO();case'reco':return rpR();case'cortex':return rpC();case'pipes':return rpP();case'apps':return rpA();case'infra':return rpI();case'ai':return rpAI();case'data':return rpD();case'bpmn':return rpB();case'soa':return rpS();case'topo':return rpT();case'log':return rpL();default:return''}}
function recoH(r){const sc={critical:'r',warning:'o',info:'b',opportunity:'g'}[r.severity]||'d';return`<div class="reco" onclick="oModal('<h2>${esc(r.title)}</h2><p>${esc(r.detail)}</p><p>${r.category} · ${r.severity} · ${r.action}</p>')"><div class="reco-dot" style="background:var(--${sc})"></div><div style="flex:1"><div class="reco-t">${r.title}</div><div class="reco-d">${r.detail}</div><div style="margin-top:4px">${tg(r.category,'p')} ${tg(r.severity,sc)}${r.auto_fixed?' <span style="color:var(--g);font-size:.62rem;font-weight:600">✓ FIXED</span>':''}</div></div></div>`}
function esc(s){return(s||'').replace(/'/g,"\\'")}
// === OVERVIEW (WEVIA Master Panel wired by Opus) ===
function rpO(){
const rec=D.recommendations||{},cx=D.cortex||{},opt=D.optimizations||{},bpmn=T?.bpmn_processes||[];
const score=rec.score||0,cls=score>=80?'g':score>=50?'o':'r',circ=2*Math.PI*42,off=circ*(1-score/100);
const totalAuto=bpmn.reduce((s,p)=>s+p.steps.filter(s=>s.status==='automated').length,0);
const totalSteps=bpmn.reduce((s,p)=>s+p.steps.length,0)||1;const autoPct=Math.round(totalAuto/totalSteps*100);
const acCirc=2*Math.PI*45,acOff=acCirc*(1-autoPct/100),acCol=autoPct>=70?'var(--g)':autoPct>=40?'var(--o)':'var(--r)';
const recs=(rec.recommendations||[]).slice(0,4);const aiOpts=T?.ai_optimizations||[];
const decs=opt.architecture_decisions||[];const commits=opt.recent_commits||[];
// === TOP 3 COLUMNS ===
let h=`<div class="ov-top">
<div class="cd ov-score"><h3>Health Score</h3>
<div class="ring" style="width:110px;height:110px;margin:8px auto"><svg width="110" height="110" viewBox="0 0 110 110"><circle cx="55" cy="55" r="46" fill="none" stroke="var(--dim2)" stroke-width="5"/><circle cx="55" cy="55" r="46" fill="none" stroke="var(--${cls})" stroke-width="5" stroke-linecap="round" stroke-dasharray="${2*Math.PI*46}" stroke-dashoffset="${2*Math.PI*46*(1-score/100)}" style="transition:stroke-dashoffset 1.2s ease"/></svg><div class="rv"><div class="ring-n" style="font-size:2.2rem;color:var(--${cls});">${score}</div><div style="font-size:.6rem;color:var(--dim);margin-top:2px">/100</div></div></div>
<div style="display:flex;gap:14px;margin-top:10px">${['critical','warning','info','opportunity'].map(s=>{const n=rec[s]||0;const co={critical:'r',warning:'o',info:'bl',opportunity:'g'}[s];return`<div style="text-align:center"><div style="font-family:var(--hf);font-size:1.1rem;font-weight:700;color:var(--${co});text-shadow:0 0 12px var(--${co})">${n}</div><div style="font-size:.52rem;color:var(--dim);text-transform:uppercase;letter-spacing:.5px;margin-top:2px">${s.substr(0,4)}</div></div>`}).join('')}</div>
</div>
<div class="cd ov-cx"><h3>CORTEX Engine</h3>
<div class="g g2" style="gap:10px;margin-top:4px">${[['FAST.PHP',cx.fast_lines,'bl'],['ROUTER',cx.router_functions,'p'],['PROVIDERS',D.ai_providers.length,'g'],['OLLAMA',D.ollama.length,'o']].map(([l,v,c])=>`<div class="cx-card"><div class="cx-val" style="color:var(--${c})">${v}</div><div class="cx-label">${l}</div></div>`).join('')}</div>
</div>
<div class="cd ov-sys"><h3>Systèmes</h3>
${[['Pipelines',(opt.pipelines||[]).length,'g'],['Agents',(opt.agents_deployed||[]).length,'p'],['Crons',D.crons?.s204_total||0,'bl'],['Docker',D.docker.length,'acc'],['Domains',D.domains.length,'y'],['SOA',T?.soa_stats?.total_services||0,'o'],['UX Tests',(D.ux_agent?.pass||0)+'/'+(D.ux_agent?.total||0),'g']].map(([l,v,c])=>`<div class="ov-sys-row"><span class="ov-sys-label">${l}</span><span class="ov-sys-val" style="color:var(--${c})">${v}</span></div>`).join('')}
</div></div>`;
// === WEVIA MASTER PANEL ===
h+=`<div class="wm">
<div>
<div class="cd"><div class="wm-hd">WEVIA Master — Actions</div>
${[{i:'S',c:'g',t:'Scan ('+D.scan_time_ms+'ms)',tm:D.generated?.substr(11,5)},{i:'T',c:'acc',t:'Topology ('+(T?.stats?.nodes||0)+' nodes)',tm:T?.generated?.substr(11,5)},{i:'R',c:'p',t:'Score '+rec.score+'/100',tm:D.generated?.substr(11,5)},{i:'F',c:'bl',t:'Auto-Fix ('+(rec.auto_fixed||0)+')',tm:D.generated?.substr(11,5)},{i:'B',c:'y',t:'BPMN ('+bpmn.length+' proc)',tm:T?.generated?.substr(11,5)}].map(a=>`<div class="wm-row"><div class="wm-dot" style="background:var(--${a.c})"></div><span class="wm-label">${a.t}</span><span class="wm-ts">${a.tm||'--'}</span></div>`).join('')}
</div>
<div class="cd" style="margin-top:14px"><div class="wm-hd">Gap Analysis</div>
${[...recs,...aiOpts.map(o=>({title:o.action,detail:o.detail,severity:o.priority==='high'?'critical':'warning',category:o.category}))].slice(0,5).map(g=>{const sc={critical:'r',warning:'o',opportunity:'g'}[g.severity]||'d';return`<div class="wm-gap"><div class="wm-gap-t">${g.title} ${tg(g.severity||'?',sc)} ${tg(g.category||'','p')}</div><div class="wm-gap-d">${g.detail||''}</div></div>`}).join('')||'<div style="color:var(--dim);font-size:.72rem;padding:8px">No gaps detected</div>'}
</div>
</div>
<div>
<div class="cd" style="text-align:center"><div class="wm-hd" style="text-align:left">Automation Coverage</div>
<div class="ring" style="width:140px;height:140px;margin:12px auto"><svg width="140" height="140" viewBox="0 0 140 140"><defs><linearGradient id="acg" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="var(--g)"/><stop offset="100%" stop-color="var(--acc)"/></linearGradient></defs><circle cx="70" cy="70" r="56" fill="none" stroke="var(--dim2)" stroke-width="7"/><circle cx="70" cy="70" r="56" fill="none" stroke="url(#acg)" stroke-width="7" stroke-linecap="round" stroke-dasharray="${2*Math.PI*56}" stroke-dashoffset="${2*Math.PI*56*(1-autoPct/100)}" transform="rotate(-90 70 70)" style="transition:stroke-dashoffset 1.2s ease"/></svg><div class="rv"><div class="ring-n" style="font-size:2.4rem;color:${acCol};text-shadow:0 0 24px ${acCol}">${autoPct}%</div></div></div>
<div style="font-size:.72rem;color:var(--dim);margin-top:8px">${totalAuto}/${totalSteps} steps · ${bpmn.length} processes</div>
</div>
<div class="cd" style="margin-top:14px"><div class="wm-hd">Timeline</div>
<div class="wm-tl">${[...decs.map(d=>({t:d.fact?.substr(0,60),d:d.created_at?.substr(0,16)})),...commits.map(c=>({t:c.msg?.substr(0,60),d:c.date?.substr(0,16)}))].slice(0,5).map(t=>`<div class="wm-tl-i"><div class="wm-tl-t">${t.t||''}</div><div class="wm-tl-d">${t.d||''}</div></div>`).join('')||'<div style="color:var(--dim);font-size:.72rem">—</div>'}</div>
</div>
<div class="cd" style="margin-top:14px"><div class="wm-hd">Control</div>
<button class="wm-btn" id="wmb" onclick="trgScan()"> TRIGGER SCAN</button>
<div style="font-size:.58rem;color:var(--dim);text-align:center;margin-top:6px;font-family:var(--hf)" id="wms">Last: ${D.generated}</div>
</div>
</div></div>`;
return h}
async function trgScan(){const b=document.getElementById('wmb'),s=document.getElementById('wms');b.disabled=true;b.textContent='SCANNING...';try{await fetch('/api/architecture-scanner.php');s.textContent='Done!';setTimeout(load,1000)}catch(e){s.textContent='Error'}setTimeout(()=>{b.disabled=false;b.textContent='▶ TRIGGER SCAN'},3000)}
function rpR(){const rec=D.recommendations||{},recs=rec.recommendations||[],decs=D.optimizations?.architecture_decisions||[];let h=`<div class="g g3"><div class="cd kpi"><div class="kpi-val" style="color:var(--${rec.score>=80?'g':'o'})">${rec.score}/100</div><div class="kpi-label">Score</div></div><div class="cd"><h3>Breakdown</h3><div class="g g2" style="gap:6px">${['critical','warning','info','opportunity'].map(s=>`<div style="text-align:center;padding:6px;background:rgba(0,229,255,.02);border-radius:6px"><div style="font-size:1rem;font-weight:700;color:var(--${{critical:'r',warning:'o',info:'b',opportunity:'g'}[s]})">${rec[s]||0}</div><div class="kpi-sub">${s}</div></div>`).join('')}</div></div><div class="cd"><h3>Auto-Fixes</h3>${(rec.fixes_log||[]).map(f=>`<div style="padding:3px 0;font-size:.72rem"><span style="color:var(--g)">✓</span> ${f.title}</div>`).join('')||'<div class="dim" style="font-size:.72rem">None this cycle</div>'}</div></div>`;
h+='<section><h2>Recommendations</h2>';recs.forEach(r=>{h+=recoH(r)});h+='</section>';
if(decs.length){h+='<section><h2>Architecture Decisions</h2>';decs.forEach(d=>{h+=`<div class="decision">${d.fact}<div style="font-size:.6rem;color:var(--dim);margin-top:2px">${d.created_at}</div></div>`});h+='</section>'}return h}
function rpC(){const cx=D.cortex||{};let h=`<div class="g g4">${kp(cx.fast_lines,'CORTEX','lines','bl')}${kp(cx.router_lines,'Router','lines','p')}${kp(cx.router_functions,'Functions','routing','g')}${kp(D.ai_providers.length,'Providers','0€','o')}</div>`;
h+='<section><h2>Smart Router</h2><div class="g g4">';[['T0: Local','weval-brain-v3','~200ms','p'],['T1: Free','Cerebras · Groq · SambaNova','~1.3s','g'],['T2: Fallback','Mistral · Cohere · Gemini','~2s','b'],['T3: Emergency','HuggingFace · Replicate','~3s','d']].forEach(([n,d,l,c])=>{h+=`<div class="cd" style="border-top:2px solid var(--${c})"><h3>${n}</h3><div style="font-size:.78rem;color:var(--t);margin:3px 0">${d}</div><div class="kpi-sub">${l}</div></div>`});
h+='</div></section><section><h2>Providers</h2><table><thead><tr><th>Provider</th><th>Model</th><th>Tier</th></tr></thead><tbody>';
D.ai_providers.forEach(p=>{h+=`<tr><td style="font-weight:600">${p.name}</td><td>${p.model}</td><td>${tg(p.tier,p.tier==='T0'?'p':p.tier==='T1'?'g':p.tier==='T2'?'b':'d')}</td></tr>`});h+='</tbody></table></section>';return h}
function rpP(){const opt=D.optimizations||{},colors=['#60a5fa','#00ff88','#ff4757','#a78bfa','#ff9f43','#fbbf24','#00e5ff','#39d2c0'];let h='<section><h2>Pipelines</h2>';(opt.pipelines||[]).forEach(p=>{h+=`<div class="pipe"><div class="pipe-dot"></div><div class="pipe-nm">${p.name}</div><div class="pipe-desc">${p.desc}</div>${p.freq?`<div class="pipe-freq">${p.freq}</div>`:''}</div>`});
h+='</section><section><h2>Agents</h2><div class="g g2">';(opt.agents_deployed||[]).forEach((a,i)=>{const bg=colors[i%8];h+=`<div class="agt"><div class="agt-icon" style="background:${bg}18;color:${bg}">A${i+1}</div><div><div style="font-weight:600;font-size:.8rem;color:var(--t)">${a.name}</div><div style="font-size:.65rem;color:var(--dim)">${a.role}</div></div>${tg(a.status,'g')}</div>`});
h+='</div></section><section><h2>Crons '+tg(D.crons?.s204_total||0,'c')+'</h2><table><thead><tr><th>Name</th><th>Freq</th><th>Script</th></tr></thead><tbody>';(D.crons?.key_crons||[]).forEach(c=>{h+=`<tr><td style="font-weight:600">${c.name}</td><td>${tg(c.freq,'c')}</td><td style="font-family:var(--hf);font-size:.62rem">${c.target}</td></tr>`});h+='</tbody></table></section>';return h}
function rpA(){let h=`<div style="display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap"><input style="padding:6px 12px;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--t);font-size:.78rem;width:200px;outline:none;font-family:var(--bf)" placeholder="Filter..." oninput="sq=this.value;document.getElementById('p-apps').innerHTML=rpA()">`;['all','authentik','internal','public'].forEach(f=>{h+=`<button class="tab${cf===f?' on':''}" onclick="cf='${f}';document.getElementById('p-apps').innerHTML=rpA()" style="padding:4px 10px;border-radius:8px">${f}</button>`});
h+='</div><table><thead><tr><th>App</th><th>Type</th><th>URL</th><th>Server</th><th>Auth</th></tr></thead><tbody>';
D.applications.filter(a=>{if(cf!=='all'&&a.auth!==cf)return false;if(sq&&!JSON.stringify(a).toLowerCase().includes(sq.toLowerCase()))return false;return true}).forEach(a=>{h+=`<tr style="cursor:pointer" onclick="oModal('<h2>${a.name}</h2><p>Type: ${a.type}</p><p>URL: ${a.url||'-'}</p><p>Port: ${a.port||'-'}</p><p>Server: ${a.server}</p><p>Auth: ${a.auth}</p>')"><td style="font-weight:600">${a.name}</td><td>${tg(a.type,'p')}</td><td style="font-family:var(--hf);font-size:.62rem">${a.url||'-'}${a.port?' :'+a.port:''}</td><td>${tg(a.server,'b')}</td><td>${tg(a.auth,a.auth==='authentik'?'g':a.auth==='public'?'c':'d')}</td></tr>`});
h+='</tbody></table>';return h}
function rpI(){let h='<section><h2>Servers</h2><div class="g g3">';D.servers.forEach(s=>{const dp=s.disk_pct||0,dc=dp>90?'r':dp>80?'o':'g';h+=`<div class="cd click" onclick="oModal('<h2>${s.id}</h2><p>IP: ${s.ip}${s.private?' / '+s.private:''}</p><p>Role: ${s.role}</p><p>Disk: ${dp}%</p><p>Uptime: ${s.uptime||'?'}</p>')"><div style="display:flex;justify-content:space-between"><span style="font-weight:700;color:var(--t)">${s.id}</span>${tg(s.nginx==='active'||s.sentinel?'UP':'?',s.nginx==='active'||s.sentinel?'g':'d')}</div><div style="font-family:var(--hf);font-size:.68rem;color:var(--acc)">${s.ip}</div><div style="display:flex;gap:12px;margin-top:4px"><div><span style="font-family:var(--hf);font-weight:700;color:var(--${dc})">${dp}%</span> <span class="dim" style="font-size:.62rem">disk</span></div>${s.disk_avail?`<div><span style="font-family:var(--hf);font-weight:700">${s.disk_avail}</span> <span class="dim" style="font-size:.62rem">free</span></div>`:''}</div><div class="bar" style="margin-top:6px"><div class="bar-f" style="width:${dp}%;background:var(--${dc})"></div></div></div>`});
h+='</div></section><section><h2>Docker '+tg(D.docker.length,'g')+'</h2><div class="g g4">';D.docker.forEach(c=>{const u=c.status.includes('Up'),hl=c.status.includes('healthy');h+=`<div class="cd click" style="padding:8px" onclick="oModal('<h2>${c.name}</h2><p>${c.status}</p>')"><div style="display:flex;justify-content:space-between;align-items:center"><span style="font-weight:600;font-size:.74rem;color:${u?'var(--t)':'var(--r)'}">${c.name}</span>${tg(hl?'healthy':u?'up':'down',hl?'g':u?'b':'r')}</div></div>`});
h+='</div></section><section><h2>Domains '+tg(D.domains.length,'b')+'</h2><table><thead><tr><th>Config</th><th>Domains</th><th>SSL</th><th>Auth</th></tr></thead><tbody>';D.domains.forEach(dm=>{h+=`<tr><td style="font-family:var(--hf);font-size:.62rem">${dm.file}</td><td style="font-size:.68rem">${dm.server_names.join(', ')}</td><td>${tg(dm.ssl?'SSL':'HTTP',dm.ssl?'g':'r')}</td><td>${tg(dm.auth_complete?'OK':dm.authentik?'MISS':'—',dm.auth_complete?'g':dm.authentik?'r':'d')}</td></tr>`});
h+='</tbody></table></section>';return h}
function rpAI(){let h=`<div class="g g4">${kp(D.ai_providers.length,'Providers','0€','g')}${kp(D.ollama.length,'Ollama','models','p')}${kp(D.qdrant.reduce((s,q)=>s+q.vectors,0).toLocaleString(),'Vectors','Qdrant','bl')}${kp('0€','Cost','monthly','acc')}</div><div class="g g2"><div class="cd"><h3>Ollama</h3><table><thead><tr><th>Model</th><th>Family</th><th>Size</th></tr></thead><tbody>`;D.ollama.forEach(m=>{h+=`<tr><td style="font-weight:600">${m.name}</td><td>${m.family}</td><td>${m.size_gb}GB</td></tr>`});h+='</tbody></table></div><div class="cd"><h3>Qdrant</h3><table><thead><tr><th>Collection</th><th>Vectors</th></tr></thead><tbody>';D.qdrant.forEach(q=>{h+=`<tr><td style="font-weight:600">${q.name}</td><td style="font-family:var(--hf)">${q.vectors.toLocaleString()}</td></tr>`});h+='</tbody></table></div></div>';return h}
function rpD(){let h=`<div class="g g3">${kp(D.wiki?.total_entries||0,'Wiki','entries','p')}${kp(D.databases?.s204?.length||0,'DBs','PostgreSQL','bl')}${kp(Object.values(D.databases?.key_tables||{}).reduce((s,v)=>s+v,0).toLocaleString(),'Records','key tables','o')}</div><div class="g g2"><div class="cd"><h3>Databases</h3><div style="display:flex;flex-wrap:wrap;gap:5px">${(D.databases?.s204||[]).map(db=>tg(db,'b')).join('')}</div></div><div class="cd"><h3>Key Tables</h3>`;Object.entries(D.databases?.key_tables||{}).forEach(([k,v])=>{h+=`<div style="display:flex;justify-content:space-between;padding:3px 0;border-bottom:1px solid rgba(0,229,255,.03);font-size:.74rem"><span>${k}</span><span style="font-family:var(--hf);color:var(--acc)">${v.toLocaleString()}</span></div>`});h+='</div></div>';return h}
function rpB(){if(!T?.bpmn_processes)return'<div style="color:var(--dim);padding:20px">Loading BPMN...</div>';const procs=T.bpmn_processes,ta=procs.reduce((s,p)=>s+p.steps.filter(s=>s.status==='automated').length,0),ts=procs.reduce((s,p)=>s+p.steps.length,0)||1;
let h=`<div class="g g3">${kp(procs.length,'Processes','BPMN','p')}${kp(Math.round(ta/ts*100)+'%','Automation','rate','g')}${kp(procs.filter(p=>p.status==='active').length+'/'+procs.length,'Active','running','bl')}</div>`;
procs.forEach((p,pi)=>{const au=p.steps.filter(s=>s.status==='automated').length,pct=Math.round(au/p.steps.length*100);
h+=`<div class="cd bpmn-card${p.status==='standby'?' standby':''}" style="margin-bottom:8px"><div class="bpmn-hd" onclick="document.getElementById('bx${pi}').classList.toggle('on');this.querySelector('.bpmn-tog').textContent=document.getElementById('bx${pi}').classList.contains('on')?'▲':'▼'"><div><span class="bpmn-nm">${p.id}${p.name}</span> ${tg(p.status,p.status==='active'?'g':'o')} ${(p.swimlanes||[]).map(s=>tg(s,'b')).join(' ')}</div><div style="display:flex;align-items:center;gap:6px"><div class="bar" style="width:50px"><div class="bar-f" style="width:${pct}%;background:var(--${pct>=80?'g':pct>=50?'o':'r'})"></div></div><span style="font-family:var(--hf);font-size:.62rem;color:var(--acc)">${pct}%</span><span class="bpmn-tog dim" style="font-size:.6rem">▼</span></div></div>`;
h+=`<div class="pn${pi===0?' on':''}" id="bx${pi}"><div class="bpmn-flow">`;p.steps.forEach((s,i)=>{const sc={automated:'g',trigger:'acc','semi-auto':'o',manual:'d',standby:'y'}[s.status]||'d';h+=`<div class="bpmn-step" onclick="oModal('<h2>${esc(s.task)}</h2><p>Actor: ${esc(s.actor)}</p><p>Type: ${s.type}</p><p>Status: ${s.status}</p>')"><div class="dot" style="width:6px;height:6px;border-radius:${s.type==='gateway'?'2px':'50%'};background:var(--${sc});box-shadow:0 0 6px var(--${sc})"></div><span style="color:var(--t)">${s.task}</span><span style="font-size:.58rem;color:var(--dim)">${s.actor}</span></div>`;if(i<p.steps.length-1)h+='<span class="bpmn-arr">→</span>'});h+='</div></div></div>'});return h}
function rpS(){if(!T?.soa_services)return'<div style="color:var(--dim);padding:20px">Loading SOA...</div>';const svc=T.soa_services,stats=T.soa_stats||{},groups={};svc.forEach(s=>{const g=s.group||'other';if(!groups[g])groups[g]=[];groups[g].push(s)});const gc={auth:'#a78bfa',ai:'#00e5ff',data:'#ff9f43',monitoring:'#39d2c0',crm:'#60a5fa',analytics:'#00ff88',chat:'#ff9f43',service:'#4a5568',api:'#ff4757',automation:'#a78bfa'};
let h=`<div class="g g3">${kp(stats.total_services||0,'Services','SOA','acc')}${kp(stats.active||0,'Active','UP','g')}${kp(Object.keys(groups).length,'Groups','categories','p')}</div>`;
Object.entries(groups).sort((a,b)=>b[1].length-a[1].length).forEach(([g,svcs])=>{h+=`<section><h2 style="color:${gc[g]||'var(--dim)'}">${g.toUpperCase()} ${tg(svcs.length,'c')}</h2><div style="display:flex;flex-wrap:wrap;gap:5px">`;svcs.forEach(s=>{const up=['healthy','active','up'].includes(s.status);h+=`<div class="soa" onclick="oModal('<h2>${s.name}</h2><p>Type: ${s.type}</p><p>Server: ${s.server}</p><p>Group: ${s.group}</p><p>Status: ${s.status}</p>')"><div class="soa-dot" style="width:5px;height:5px;border-radius:50%;background:${up?'var(--g)':'var(--r)'};box-shadow:0 0 6px ${up?'var(--g)':'var(--r)'}"></div><span style="font-weight:600;color:var(--t)">${s.name}</span>${tg(s.server,'b')}</div>`});h+='</div></section>'});return h}
function rpT(){if(!T)return'<div style="color:var(--dim);padding:20px">Loading...</div>';const nodes=T.nodes||[],edges=T.edges||[],groups={};nodes.forEach(n=>{const g=n.group||'other';if(!groups[g])groups[g]=[];groups[g].push(n)});const gc={server:'#60a5fa',auth:'#a78bfa',ai:'#00e5ff',data:'#ff9f43',nginx:'#fbbf24',monitoring:'#39d2c0',crm:'#60a5fa',analytics:'#00ff88',chat:'#ff9f43',cloud:'#00e5ff',service:'#4a5568',api:'#ff4757',automation:'#a78bfa'};
let h=`<div class="g g3">${kp(T.stats.nodes,'Nodes','discovered','acc')}${kp(T.stats.edges,'Edges','connections','bl')}${kp(T.stats.scan_ms+'ms','Scan','time','g')}</div>`;
h+='<div style="display:flex;flex-wrap:wrap;gap:5px;margin:8px 0">';Object.entries(groups).forEach(([g,ns])=>{h+=`<div style="display:flex;align-items:center;gap:3px;padding:2px 8px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.62rem"><div style="width:5px;height:5px;border-radius:50%;background:${gc[g]||'#4a5568'}"></div>${g} (${ns.length})</div>`});h+='</div>';
Object.entries(groups).sort((a,b)=>b[1].length-a[1].length).forEach(([g,ns])=>{h+=`<div style="margin-bottom:10px"><div style="font-family:var(--hf);font-size:.6rem;color:${gc[g]||'var(--dim)'};text-transform:uppercase;letter-spacing:1px;margin-bottom:4px">${g} (${ns.length})</div><div style="display:flex;flex-wrap:wrap;gap:4px">`;ns.forEach(n=>{const sc=['healthy','up','active'].includes(n.status);h+=`<div class="soa" onclick="oModal('<h2>${n.label}</h2><p>Type: ${n.type}</p><p>Server: ${n.server}</p><p>Status: ${n.status}</p>${n.ip?'<p>IP: '+n.ip+'</p>':''}')"><div class="soa-dot" style="width:5px;height:5px;border-radius:50%;background:${sc?'var(--g)':'var(--r)'}"></div><span style="color:var(--t)">${n.label}</span></div>`});h+='</div></div>'});
h+=`<section style="margin-top:14px"><h2>Connections ${tg(edges.length,'c')}</h2><table><thead><tr><th>From</th><th>To</th><th>Port</th><th>Type</th></tr></thead><tbody>`;edges.forEach(e=>{h+=`<tr><td style="font-size:.68rem">${e.from.replace(/^(docker_|nginx_|port_|service_)/,'')}</td><td style="font-size:.68rem">${e.to.replace(/^(docker_|nginx_|port_|service_)/,'')}</td><td style="font-family:var(--hf);font-size:.6rem;color:var(--acc)">${e.label||''}</td><td>${tg(e.type,e.type==='proxy'?'b':e.type==='auth'?'p':'d')}</td></tr>`});h+='</tbody></table></section>';return h}
function rpL(){const commits=D.optimizations?.recent_commits||[],fixes=D.optimizations?.auto_fixes||[];let h='<div class="g g2"><div class="cd"><h3>Git '+tg(commits.length,'c')+'</h3>';commits.forEach(c=>{h+=`<div class="commit"><span class="commit-h">${c.hash}</span><span class="commit-m">${c.msg}</span></div>`});if(!commits.length)h+='<div class="dim" style="font-size:.72rem">—</div>';h+='</div><div class="cd"><h3>Auto-Fixes '+tg(fixes.length,'g')+'</h3>';fixes.forEach(f=>{h+=`<div class="decision" style="border-color:var(--g)">${f.fact}<div style="font-size:.58rem;color:var(--dim);margin-top:2px">${f.created_at}</div></div>`});if(!fixes.length)h+='<div class="dim" style="font-size:.72rem">—</div>';h+='</div></div>';return h}
load();setInterval(load,5*60*1000);
</script>
<!-- NL-AutoWire-badge-v1 -->
<a href="/nl-autowire-status.html" id="nlAutowireBadge" style="position:fixed;bottom:8px;right:8px;background:#1a3d2c;color:#56d364;padding:4px 10px;border-radius:12px;font-size:0.78em;font-family:-apple-system,sans-serif;text-decoration:none;z-index:99999;border:1px solid rgba(86,211,100,0.5);box-shadow:0 2px 8px rgba(0,0,0,0.3);">NL-AutoWire &check;</a>
<!-- CARTO_REMOVED -->
<!-- CARTO_BANNER_V1 -->
<div style="position:fixed;bottom:20px;right:20px;z-index:9999;background:linear-gradient(135deg,#141931,#2d1b5e);border:1px solid #64ffda;border-radius:12px;padding:12px 18px;box-shadow:0 4px 20px rgba(100,255,218,.3);font-family:-apple-system,Segoe UI,sans-serif;font-size:13px">
<a href="/cartographie-screens.html" style="color:#64ffda;text-decoration:none;font-weight:600;display:flex;align-items:center;gap:8px" title="Cartographie exhaustive de tous les ecrans live">
<span style="font-size:18px">&#128506;</span> Cartographie live
<span id="carto-banner-count" style="color:#8892b0;font-size:11px">3914 ecrans</span>
</a>
</div>
<script>
(function(){
fetch('/api/screens-health.php?_='+Date.now(),{cache:'no-store'}).then(r=>r.json()).then(d=>{
const c=d.counts||{}; const up=c.UP||0; const slow=c.SLOW||0; const br=c.BROKEN||0;
const el=document.getElementById('carto-banner-count');
if(el) el.innerHTML=`<span style="color:#22c55e">${up} UP</span> / <span style="color:#f59e0b">${slow} Lent</span> / <span style="color:#ef4444">${br} 5xx</span>`;
}).catch(()=>{});
})();
</script>
<!-- /CARTO_BANNER_V1 -->
<!-- WTP-GAP-FILL-V1 (doctrine 90-v2 gap-fill showcase, 18avr 2026) -->
<style>
.wtp-gapfill-banner{position:fixed;bottom:0;left:0;right:0;z-index:99999;background:linear-gradient(90deg,#05060a,#0b0d15 20%,#181d2e 50%,#0b0d15 80%,#05060a);border-top:2px solid #14b8a6;color:#e2e8f0;padding:10px 16px;font-family:Inter,system-ui,-apple-system,sans-serif;font-size:11.5px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;box-shadow:0 -10px 30px rgba(20,184,166,.28)}
.wtp-gapfill-banner a{color:#5eead4;text-decoration:none;font-weight:600;transition:color .15s}
.wtp-gapfill-banner a:hover{color:#22d3ee}
.wtp-gapfill-banner .pill{padding:2px 9px;background:rgba(99,102,241,.14);color:#a5b4fc;border-radius:10px;font-size:10.5px;font-family:JetBrains Mono,monospace;font-weight:600}
.wtp-gapfill-banner .pill.new{background:rgba(20,184,166,.22);color:#5eead4}
.wtp-gapfill-banner .pill.hot{background:rgba(236,72,153,.22);color:#f472b6}
.wtp-gapfill-banner .close{margin-left:auto;cursor:pointer;color:#64748b;padding:0 8px;font-size:16px;line-height:1;border:1px solid #334155;border-radius:4px}
.wtp-gapfill-banner .close:hover{color:#e2e8f0;border-color:#64748b}
.wtp-gapfill-banner.hidden{display:none}
@media(max-width:768px){.wtp-gapfill-banner{font-size:10px;padding:7px 10px;gap:8px}}
</style>
<div class="wtp-gapfill-banner" id="wtpGapFillBanner">
<span>🎯 <strong>WEVAL Agents Gap-Fill ERP</strong></span>
<span class="pill hot">45 gaps</span>
<span class="pill">SAP · Oracle · NetSuite · Dynamics</span>
<span class="pill new">🆕 Meeting Rooms</span>
<span class="pill new">🆕 Lean 6 Sigma</span>
<span id="wtp-gfb-metrics" class="pill">— chargement —</span>
<a href="/weval-technology-platform.html">→ WTP Portal (16 mod)</a>
<a href="/enterprise-model.html">Enterprise Model</a>
<a href="/api/weval-agents-gap-fill-manifest.json" target="_blank">📋 Manifest</a>
<span class="close" onclick="document.getElementById("wtpGapFillBanner").classList.add("hidden");localStorage.setItem("wtpGapFillHidden","1")">×</span>
</div>
<script>
(async()=>{
if(localStorage.getItem("wtpGapFillHidden")==="1"){document.getElementById("wtpGapFillBanner").classList.add("hidden");return;}
try{
const r=await fetch("/api/source-of-truth.json?t="+Date.now());
const d=await r.json();
const el=document.getElementById("wtp-gfb-metrics");
if(el)el.textContent=(d.ethica_total||"?")+" HCPs · "+(d.nonreg||"?")+" · "+(d.providers_count||"?")+" IA · "+(d.agents_count||"?")+" agents · "+(d.docker_running||"?")+" 🐳";
}catch(e){}
})();
</script>
<!-- WTP-D90V2-ENRICH-BANNER (doctrine 90-v2, 17avr 2026) -->
<style>
.wtp-enrich-banner{position:fixed;bottom:0;left:0;right:0;z-index:9999;background:linear-gradient(90deg,#0b0d15,#181d2e,#0b0d15);border-top:2px solid #14b8a6;color:#e2e8f0;padding:9px 18px;font-family:'JetBrains Mono',monospace,-apple-system,system-ui;font-size:11.5px;display:flex;align-items:center;gap:14px;flex-wrap:wrap;box-shadow:0 -8px 24px rgba(20,184,166,.25)}
.wtp-enrich-banner a{color:#14b8a6;text-decoration:none;font-weight:600}
.wtp-enrich-banner a:hover{color:#22d3ee}
.wtp-enrich-banner .pill{padding:2px 8px;background:rgba(99,102,241,.15);color:#a5b4fc;border-radius:10px;font-size:10.5px}
.wtp-enrich-banner .pill.new{background:rgba(20,184,166,.2);color:#5eead4}
.wtp-enrich-banner .close{margin-left:auto;cursor:pointer;color:#64748b;padding:0 6px;font-size:14px}
.wtp-enrich-banner .close:hover{color:#e2e8f0}
.wtp-enrich-banner.hidden{display:none}
@media(max-width:768px){.wtp-enrich-banner{font-size:10px;padding:6px 10px}}
</style>
<div class="wtp-enrich-banner" id="wtpEnrichBanner">
<span>🏛️ <strong>Enterprise Model 16 depts</strong></span>
<span class="pill new">🆕 Meeting Rooms</span>
<span class="pill new">🆕 Lean 6 Sigma</span>
<span id="wtp-eb-metrics" class="pill">— chargement —</span>
<a href="/weval-technology-platform.html">→ WEVAL Technology Platform (16 modules)</a>
<a href="/enterprise-model.html">Enterprise Model</a>
<a href="/wevia-master.html">WEVIA Master</a>
<span class="close" onclick="document.getElementById('wtpEnrichBanner').classList.add('hidden')">×</span>
</div>
<script>
(async()=>{try{const r=await fetch('/api/source-of-truth.json?t='+Date.now());const d=await r.json();const el=document.getElementById('wtp-eb-metrics');if(el)el.textContent=(d.ethica_total||'?')+' HCPs · '+(d.nonreg||'?')+' · '+(d.providers_count||'?')+' IA · '+(d.docker_running||'?')+' 🐳 · '+(d.subdomains_live||'?')+' subdomains';}catch(e){}})();
</script>
<!-- V66-PAIN-POINTS-ATLAS -->
<div style="position:fixed;bottom:18px;right:18px;z-index:999;background:linear-gradient(135deg,#eab308,#f59e0b);border-radius:10px;padding:12px 16px;box-shadow:0 12px 32px rgba(234,179,8,0.4);display:flex;gap:10px;align-items:center;max-width:360px;text-decoration:none">
<span style="font-size:22px">🗺️</span>
<div style="color:#0b0d15">
<div style="font-size:12px;font-weight:800">Pain Points Atlas · 25 ERPs</div>
<div style="font-size:10.5px;opacity:.85">35 pain points · 35 agents · 17.36M€ savings/client</div>
<a href="/pain-points-atlas.html" style="display:inline-block;margin-top:4px;padding:3px 10px;background:#0b0d15;color:#eab308;border-radius:5px;font-size:10.5px;font-weight:700;text-decoration:none">Open Atlas →</a>
</div>
</div>
<!-- /V66-PAIN-POINTS-ATLAS -->
<!-- Living Proof integration 18avr26 -->
<div style="position:fixed;bottom:18px;right:18px;background:rgba(16,24,40,.92);border:1px solid rgba(120,140,255,.3);border-radius:12px;padding:10px 14px;font-family:-apple-system,sans-serif;font-size:12px;color:#e5edff;backdrop-filter:blur(10px);z-index:9999;box-shadow:0 8px 24px rgba(0,0,0,.4)">
<a href="/living-proof.html" style="color:#60a5fa;text-decoration:none;display:flex;align-items:center;gap:8px">🎬 Living Proof<span style="font-size:10px;color:#8ca6cc">Video Testing</span></a>
</div>
<script>
/* V75 AVATAR UNIFIER — Meeting-rooms emoji style (Opus 19avr) */
(function() {
if (window.__WEVAL_AVATAR_V75) return;
window.__WEVAL_AVATAR_V75 = true;
const REG_URL = '/api/agent-avatars.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 getAvatarUrl(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 findCI(key) {
const lower = key.toLowerCase();
for (const k of Object.keys(REG)) if (k.toLowerCase() === lower) return k;
return null;
}
function apply() {
document.querySelectorAll('img').forEach(img => {
const key = img.alt || img.dataset.agent || img.dataset.name || img.title || '';
if (!key) return;
let url = getAvatarUrl(key);
if (!url) { const alt = findCI(key); if (alt) url = getAvatarUrl(alt); }
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 = getAvatarUrl(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;background:transparent';
el.setAttribute('data-weval-v75-applied', '1');
el.prepend(img);
});
}
apply();
setTimeout(apply, 400); setTimeout(apply, 1200); setTimeout(apply, 3000);
const mo = new MutationObserver(() => apply());
mo.observe(document.body, {childList: true, subtree: true});
setTimeout(() => mo.disconnect(), 20000);
console.log('[V75 AvatarUnifier] applied from', Object.keys(REG).length, 'agents');
}).catch(e => console.warn('[V75] fetch failed', e));
})();
</script>
</body>
</html>