730 lines
66 KiB
HTML
730 lines
66 KiB
HTML
<!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>
|
||
<script src="/js/wevia-a11y-auto.js" defer></script>
|
||
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-143816 -->
|
||
<style id="doctrine60-ux-direct">
|
||
|
||
/* DOCTRINE-60-UX-ENRICH injected-direct */
|
||
body::before {
|
||
content: '';
|
||
position: fixed;
|
||
top: 0; left: 0; width: 100vw; height: 100vh;
|
||
background: radial-gradient(circle at 50% 50%, rgba(100,180,255,0.08), transparent 60%);
|
||
pointer-events: none;
|
||
z-index: -1;
|
||
}
|
||
.card, .kpi, .panel, .btn {
|
||
transition: all 0.3s cubic-bezier(0.2,0,0.1,1);
|
||
}
|
||
.card:hover, .kpi:hover, .panel:hover {
|
||
box-shadow: 0 4px 20px rgba(100,180,255,0.2);
|
||
border-color: rgba(100,180,255,0.5);
|
||
}
|
||
@keyframes pulseD60 {
|
||
0%,100% { opacity: 1; transform: scale(1); }
|
||
50% { opacity: 0.7; transform: scale(1.05); }
|
||
}
|
||
.pulse, .live-indicator, .active, .online {
|
||
animation: pulseD60 3s ease-in-out infinite;
|
||
}
|
||
.modal, .chat, .speech, .overlay {
|
||
backdrop-filter: blur(12px);
|
||
-webkit-backdrop-filter: blur(12px);
|
||
}
|
||
.enter-stagger {
|
||
animation: enterStagD60 0.5s cubic-bezier(0.2,0,0.1,1) forwards;
|
||
}
|
||
@keyframes enterStagD60 {
|
||
from { opacity: 0; transform: translateY(20px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
</style>
|
||
</head>
|
||
<body style="padding-top:60px;padding-bottom:64px">
|
||
<!-- BETON-DOCTRINE-101 dual-dummy block (pages pub) -->
|
||
<div id="weval-global-logout" style="display:none!important;visibility:hidden!important" aria-hidden="true" data-beton-101="dummy-to-block-auto-injection"></div>
|
||
<a id="weval-gl" href="#" style="display:none!important;visibility:hidden!important" aria-hidden="true" data-beton-101="dummy-to-block-auto-injection" tabindex="-1"></a>
|
||
<div 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-v4','~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 ✓</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">🗺</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}}
|
||
|
||
/* === WEVIA Gemini Rolling v2 VISIBLE Enrichment (wave 306 batch) === */
|
||
.kpi,[class*="card"],[class*="panel"],[class*="room"],.stat-card,.metric-card,.hub-card,.widget,.stat,.box{position:relative!important}
|
||
.kpi,[class*="card"],.stat-card,.metric-card,.hub-card{animation:geV2Entrance .8s cubic-bezier(.34,1.56,.64,1) backwards}
|
||
.kpi:nth-child(1),[class*="card"]:nth-child(1){animation-delay:0s}
|
||
.kpi:nth-child(2),[class*="card"]:nth-child(2){animation-delay:.09s}
|
||
.kpi:nth-child(3),[class*="card"]:nth-child(3){animation-delay:.18s}
|
||
.kpi:nth-child(4),[class*="card"]:nth-child(4){animation-delay:.27s}
|
||
.kpi:nth-child(5),[class*="card"]:nth-child(5){animation-delay:.36s}
|
||
.kpi:nth-child(6),[class*="card"]:nth-child(6){animation-delay:.45s}
|
||
@keyframes geV2Entrance{from{opacity:0;transform:translateY(24px) scale(.94)}to{opacity:1;transform:translateY(0) scale(1)}}
|
||
.kpi,[class*="card"],.stat-card,.metric-card,.hub-card,.widget{border:1px solid transparent!important;box-shadow:0 0 0 1px rgba(236,72,153,.15),0 4px 16px rgba(0,0,0,.25)!important;transition:box-shadow .4s,transform .3s cubic-bezier(.34,1.56,.64,1),filter .3s!important}
|
||
.kpi:hover,[class*="card"]:hover,.stat-card:hover,.metric-card:hover,.hub-card:hover{transform:translateY(-6px) scale(1.03)!important;filter:brightness(1.2)!important;box-shadow:0 0 0 2px rgba(236,72,153,.6),0 12px 32px rgba(236,72,153,.25),0 0 24px rgba(78,205,196,.2)!important}
|
||
.kpi::before,[class*="card"]::before,.stat-card::before,.metric-card::before,.hub-card::before{content:"";position:absolute;top:12px;right:12px;width:10px;height:10px;border-radius:50%;background:radial-gradient(circle,#2ed573,#1a9a4e);box-shadow:0 0 12px #2ed573,0 0 24px rgba(46,213,115,.5);animation:geV2Pulse 1.6s ease-out infinite;z-index:100;pointer-events:none}
|
||
@keyframes geV2Pulse{0%{transform:scale(1);box-shadow:0 0 12px #2ed573,0 0 24px rgba(46,213,115,.5)}50%{transform:scale(1.4);box-shadow:0 0 20px #2ed573,0 0 40px rgba(46,213,115,.8)}100%{transform:scale(1);box-shadow:0 0 12px #2ed573,0 0 24px rgba(46,213,115,.5)}}
|
||
body::after{content:"";position:fixed;inset:0;pointer-events:none;background:radial-gradient(ellipse at 70% 30%,transparent 40%,rgba(236,72,153,.06) 100%),radial-gradient(ellipse at 30% 70%,transparent 40%,rgba(78,205,196,.04) 100%);animation:geV2Ambient 10s ease-in-out infinite;z-index:0}
|
||
@keyframes geV2Ambient{0%,100%{opacity:.5}50%{opacity:1}}
|
||
h1,.header-title,.main-title,.hub-title,.page-title{background-image:linear-gradient(90deg,currentColor 0%,currentColor 40%,rgba(236,72,153,1) 50%,currentColor 60%,currentColor 100%)!important;background-size:200% auto!important;-webkit-background-clip:text!important;background-clip:text!important;-webkit-text-fill-color:transparent!important;animation:geV2Shimmer 5s linear infinite!important}
|
||
@keyframes geV2Shimmer{0%{background-position:200% center}100%{background-position:-200% center}}
|
||
/* Doctrine zero chevauchement - hide common offenders */
|
||
.opus-x-btn,.toggle-top-right-btn,.fab-corner{display:none!important}
|
||
/* === end WEVIA Gemini Rolling v2 batch === */
|
||
</style>
|
||
<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-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 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>
|
||
|
||
<!-- === OPUS UNIVERSAL DRILL-DOWN v1 19avr — append-only, doctrine #14 === -->
|
||
<script>
|
||
(function(){
|
||
if (window.__opusUniversalDrill) return; window.__opusUniversalDrill = true;
|
||
var d = document;
|
||
var m = d.createElement('div');
|
||
m.id = 'opus-udrill';
|
||
m.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.82);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:99995;padding:20px;cursor:pointer';
|
||
var inner = d.createElement('div');
|
||
inner.id = 'opus-udrill-in';
|
||
inner.style.cssText = 'max-width:900px;width:100%;max-height:90vh;overflow:auto;background:#0b0d15;border:1px solid rgba(99,102,241,0.35);border-radius:14px;padding:28px;cursor:default;box-shadow:0 20px 60px rgba(0,0,0,0.6);color:#e2e8f0;font:14px/1.55 Inter,system-ui,sans-serif';
|
||
inner.addEventListener('click', function(e){ e.stopPropagation(); });
|
||
m.appendChild(inner);
|
||
m.addEventListener('click', function(){ m.style.display='none'; });
|
||
d.addEventListener('keydown', function(e){ if(e.key==='Escape') m.style.display='none'; });
|
||
(d.body || d.documentElement).appendChild(m);
|
||
|
||
function openCard(card) {
|
||
// Clone card content + show close btn + increase font-size
|
||
var html = '<div style="display:flex;justify-content:flex-end;margin-bottom:14px"><button id="opus-udrill-close" style="padding:6px 14px;background:#171b2a;border:1px solid rgba(99,102,241,0.25);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:12px">✕ Fermer (Esc)</button></div>';
|
||
html += '<div style="transform-origin:top left;font-size:1.05em">' + card.outerHTML + '</div>';
|
||
inner.innerHTML = html;
|
||
d.getElementById('opus-udrill-close').onclick = function(){ m.style.display='none'; };
|
||
m.style.display = 'flex';
|
||
}
|
||
|
||
function wire(root) {
|
||
var sels = '.card,[class*="card"],.kpi,[class*="kpi"],.stat,[class*="stat"],.tile,[class*="tile"],.metric,[class*="metric"],.widget,[class*="widget"]';
|
||
var cards = root.querySelectorAll(sels);
|
||
for (var i = 0; i < cards.length; i++) {
|
||
var c = cards[i];
|
||
if (c.__opusWired) continue;
|
||
if (c.closest('button, a, input, select, textarea, #opus-udrill')) continue;
|
||
var r = c.getBoundingClientRect();
|
||
if (r.width < 60 || r.height < 40) continue;
|
||
c.__opusWired = true;
|
||
c.style.cursor = 'pointer';
|
||
c.setAttribute('role','button');
|
||
c.setAttribute('tabindex','0');
|
||
c.addEventListener('click', function(ev){
|
||
// If a more-specific drill is already active (e.g. pp-card custom), let it handle
|
||
if (ev.target.closest('[data-pp-id]') && window.__opusDrillInit) return;
|
||
if (ev.target.closest('a,button,input,select')) return;
|
||
ev.preventDefault(); ev.stopPropagation();
|
||
openCard(this);
|
||
});
|
||
c.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();openCard(this);} });
|
||
}
|
||
}
|
||
|
||
// Initial + mutation observer
|
||
var initRun = function(){ wire(d.body || d.documentElement); };
|
||
if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', initRun);
|
||
else initRun();
|
||
var mo = new MutationObserver(function(muts){
|
||
var newCard = false;
|
||
for (var i=0;i<muts.length;i++) if (muts[i].addedNodes.length) { newCard = true; break; }
|
||
if (newCard) initRun();
|
||
});
|
||
mo.observe(d.body || d.documentElement, {childList:true, subtree:true});
|
||
})();
|
||
</script>
|
||
<!-- === OPUS UNIVERSAL DRILL-DOWN END === -->
|
||
|
||
|
||
<script src="/api/a11y-auto-enhancer.js" defer></script>
|
||
<!-- WTP_UDOCK_V1 (Opus 21-avr tour30) --><script src="/wtp-unified-dock.js" defer></script>
|
||
<script src="/opus-antioverlap-doctrine.js?v=1776776094" defer></script>
|
||
<!-- DOCTRINE-60-UX-JS --><script id="doctrine60-ux-js-direct">
|
||
|
||
// DOCTRINE-60-UX-JS staggered entrance
|
||
(function(){
|
||
if (!('IntersectionObserver' in window)) return;
|
||
const obs = new IntersectionObserver((entries) => {
|
||
entries.forEach((e, i) => {
|
||
if (e.isIntersecting) {
|
||
setTimeout(() => e.target.classList.add('enter-stagger'), i * 80);
|
||
obs.unobserve(e.target);
|
||
}
|
||
});
|
||
});
|
||
document.querySelectorAll('.card, .kpi, .panel').forEach(el => obs.observe(el));
|
||
})();
|
||
|
||
</script>
|
||
</body>
|
||
</html>
|