Files
html/dg-command-center.html
Opus 0478ed2d00
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
phase44 doctrine 182 inject 17 pages PRIO2 dashboards+monitors - 92 pages UX total
17 pages injectees via inject-d60-direct.py:
- acquired-dashboard brain-center-tenant claude-monitor
- command-center contacts-segmentation-dashboard crm-dashboard-live
- crons-monitor dashboards-index database-dashboard-live
- dg-command-center director-center e2e-dashboard em-dashboard
- ethica-dashboard-live ethica-monitor infra-dashboard-live
- lean6sigma-dashboard

Total session cumul: 92 pages UX doctrine 60 (75 + 17)
Tags Opus: 30
Doctrines vault: 27 (146-182)

ZERO regression. Train multi-Claude sync:
- autre Claude opus_disaster_recovery (187 intents)
- autre Claude opus_cyber_profile_manager
- NR 153/153 invariant
2026-04-24 14:19:03 +02:00

657 lines
34 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.0">
<title>WEVAL · DG Command Center — Real-time Pilotage</title>
<style>
:root {
--bg-0:#05060a; --bg-1:#0b0d15; --bg-2:#11141f; --bg-3:#171b2a;
--border:rgba(99,102,241,0.15); --border-h:rgba(99,102,241,0.35);
--text:#e2e8f0; --dim:#94a3b8; --mute:#64748b;
--accent:#14b8a6; --accent2:#6366f1; --purple:#a855f7; --cyan:#06b6d4;
--ok:#22c55e; --warn:#f59e0b; --err:#ef4444; --rose:#f43f5e; --gold:#eab308;
}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Inter',system-ui,sans-serif;background:radial-gradient(ellipse at top,#0f1420,#05060a 60%);color:var(--text);min-height:100vh;font-size:13px;line-height:1.5}
.container{max-width:1760px;margin:0 auto;padding:24px 28px 80px}
/* HEADER */
header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:20px;padding-bottom:16px;border-bottom:1px solid var(--border)}
header h1{font-size:26px;font-weight:800;background:linear-gradient(90deg,#22d3ee,#a855f7,#eab308);-webkit-background-clip:text;background-clip:text;color:transparent;letter-spacing:-0.4px;display:flex;align-items:center;gap:10px}
header .sub{color:var(--dim);font-size:12.5px;margin-top:5px}
header .clock{font-family:'JetBrains Mono',monospace;color:var(--accent);font-size:11px;margin-top:4px}
.actions{display:flex;gap:8px}
.btn{padding:7px 13px;background:var(--bg-2);border:1px solid var(--border);color:var(--text);border-radius:8px;font-size:11.5px;cursor:pointer;text-decoration:none;font-family:inherit;transition:all .2s}
.btn:hover{border-color:var(--accent);color:var(--accent)}
.pulse{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--ok);box-shadow:0 0 0 0 rgba(34,197,94,.7);animation:pulse 2s infinite}
@keyframes pulse{0%,100%{box-shadow:0 0 0 0 rgba(34,197,94,.7)}70%{box-shadow:0 0 0 8px rgba(34,197,94,0)}}
/* LAYOUT GRID */
.row{display:grid;gap:14px;margin-bottom:14px}
.row-4{grid-template-columns:repeat(4,1fr)}
.row-3{grid-template-columns:repeat(3,1fr)}
.row-2{grid-template-columns:2fr 1fr}
.row-2e{grid-template-columns:1fr 1fr}
@media(max-width:1200px){.row-4,.row-3,.row-2,.row-2e{grid-template-columns:1fr 1fr}}
@media(max-width:720px){.row-4,.row-3,.row-2,.row-2e{grid-template-columns:1fr}}
/* CARDS */
.card{background:var(--bg-1);border:1px solid var(--border);border-radius:12px;padding:16px;position:relative;overflow:hidden}
.card.span-2{grid-column:span 2}
.card.span-3{grid-column:span 3}
.card.span-4{grid-column:span 4}
.card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
.card-title{font-size:11.5px;color:var(--dim);text-transform:uppercase;letter-spacing:0.6px;font-weight:700;display:flex;align-items:center;gap:6px}
.card-badge{font-size:9.5px;padding:2px 7px;border-radius:8px;font-weight:700;letter-spacing:0.3px;background:rgba(20,184,166,0.15);color:#5eead4}
.card-badge.warn{background:rgba(245,158,11,.18);color:#fbbf24}
.card-badge.danger{background:rgba(239,68,68,.18);color:#fca5a5}
.card-badge.info{background:rgba(99,102,241,.18);color:#a5b4fc}
/* KPI big */
.kpi-big{font-size:32px;font-weight:800;letter-spacing:-0.5px;line-height:1}
.kpi-big.gold{background:linear-gradient(135deg,var(--gold),var(--warn));-webkit-background-clip:text;background-clip:text;color:transparent}
.kpi-big.ok{color:var(--ok)}
.kpi-big.warn{color:var(--warn)}
.kpi-big.danger{color:var(--err)}
.kpi-sub{color:var(--dim);font-size:11px;margin-top:4px}
/* ALERTS DG STRIP */
.alerts-strip{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:10px;margin-bottom:20px}
.alert-card{background:var(--bg-1);border:1px solid var(--border);border-radius:10px;padding:14px 16px;border-left:4px solid var(--warn);position:relative;transition:all .2s}
.alert-card:hover{border-color:var(--border-h);transform:translateY(-2px)}
.alert-card.critical{border-left-color:var(--err);background:linear-gradient(135deg,rgba(239,68,68,0.06),var(--bg-1))}
.alert-card.high{border-left-color:var(--warn)}
.alert-card.medium{border-left-color:var(--cyan)}
.alert-head{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:6px}
.alert-title{font-size:13px;font-weight:700;color:var(--text);display:flex;gap:7px;align-items:flex-start}
.alert-lvl{font-size:9px;padding:2px 6px;border-radius:6px;font-weight:700;letter-spacing:0.3px;text-transform:uppercase;flex-shrink:0}
.alert-lvl.critical{background:rgba(239,68,68,0.2);color:#fca5a5}
.alert-lvl.high{background:rgba(245,158,11,0.2);color:#fbbf24}
.alert-lvl.medium{background:rgba(6,182,212,0.18);color:#7dd3fc}
.alert-detail{font-size:11.5px;color:var(--dim);margin:6px 0;line-height:1.45}
.alert-foot{display:flex;justify-content:space-between;align-items:center;margin-top:8px;font-size:10.5px}
.alert-foot .deadline{color:var(--warn);font-weight:600}
.alert-foot a{color:var(--accent);text-decoration:none;font-weight:600}
/* TOC streams */
.toc-wrap{display:flex;flex-direction:column;gap:8px}
.toc-stream{display:grid;grid-template-columns:28px 1fr 60px 80px 1fr;gap:10px;align-items:center;padding:8px 10px;background:var(--bg-2);border-radius:8px;border-left:3px solid var(--dim)}
.toc-stream.bottleneck{border-left-color:var(--err);background:linear-gradient(135deg,rgba(239,68,68,0.06),var(--bg-2));box-shadow:0 0 0 1px rgba(239,68,68,0.25)}
.toc-stream.flow{border-left-color:var(--ok)}
.toc-stream.starved{border-left-color:var(--cyan)}
.toc-icon{font-size:18px;text-align:center}
.toc-label{font-size:12px;font-weight:600;color:var(--text)}
.toc-label .small{color:var(--dim);font-size:10px;font-weight:400;margin-top:2px}
.toc-throughput{font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:800;color:var(--text);text-align:center}
.toc-bar-wrap{background:var(--bg-3);height:10px;border-radius:5px;overflow:hidden}
.toc-bar-fill{height:100%;background:linear-gradient(90deg,#14b8a6,#6366f1);transition:width 1.2s cubic-bezier(.4,0,.2,1)}
.toc-bar-fill.bot{background:linear-gradient(90deg,#ef4444,#f59e0b)}
.toc-util{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--dim);text-align:right;font-weight:600}
.toc-constraint{font-size:10px;color:var(--mute);font-style:italic;grid-column:1/-1;padding-left:38px;margin-top:-3px}
/* Funnel */
.funnel-wrap{display:flex;flex-direction:column;gap:6px;align-items:center;padding:10px 0}
.funnel-row{display:grid;grid-template-columns:160px 1fr 70px 50px;gap:10px;align-items:center;width:100%;font-size:12px}
.funnel-label{color:var(--text);font-weight:500;font-size:11.5px}
.funnel-bar-wrap{background:var(--bg-3);height:28px;border-radius:4px;overflow:hidden;position:relative}
.funnel-bar{height:100%;transition:width 1.2s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;padding-left:10px;font-size:11.5px;font-weight:700;color:white}
.funnel-count{font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:700;color:var(--text);text-align:right}
.funnel-conv{font-family:'JetBrains Mono',monospace;font-size:10.5px;color:var(--dim);text-align:right}
.funnel-conv.warn{color:var(--warn)}
.funnel-conv.danger{color:var(--err)}
/* Data pipelines */
.dp-wrap{display:grid;grid-template-columns:1fr;gap:6px}
.dp-row{display:grid;grid-template-columns:1fr 80px 1fr 60px;gap:10px;align-items:center;padding:7px 10px;background:var(--bg-2);border-radius:6px;font-size:11.5px}
.dp-name{color:var(--text);font-weight:500}
.dp-vol{font-family:'JetBrains Mono',monospace;font-weight:700;text-align:right;color:var(--text)}
.dp-bar-wrap{background:var(--bg-3);height:8px;border-radius:4px;overflow:hidden}
.dp-bar-fill{height:100%;background:linear-gradient(90deg,var(--ok),var(--cyan));transition:width 1.2s}
.dp-status{font-size:10.5px;text-align:right;font-family:'JetBrains Mono',monospace}
.dp-status.ok{color:var(--ok)} .dp-status.warn{color:var(--warn)} .dp-status.danger{color:var(--err)}
/* Marketing grid */
.mkt-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}
@media(max-width:900px){.mkt-grid{grid-template-columns:repeat(2,1fr)}}
.mkt-cell{background:var(--bg-2);border-radius:6px;padding:10px;border-left:2px solid var(--purple)}
.mkt-cell .l{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:0.4px;font-weight:600}
.mkt-cell .v{font-size:17px;font-weight:800;color:var(--text);font-family:'JetBrains Mono',monospace;margin-top:3px;line-height:1}
.mkt-cell .u{font-size:10.5px;color:var(--dim);margin-left:2px}
/* CRM view */
.crm-stage-row{display:grid;grid-template-columns:110px 50px 1fr 80px;gap:10px;align-items:center;padding:7px 10px;background:var(--bg-2);border-radius:6px;font-size:11.5px;margin-bottom:5px}
.stage-label{font-weight:600;color:var(--text)}
.stage-count{font-family:'JetBrains Mono',monospace;font-weight:700;color:var(--accent);text-align:center}
.stage-bar-wrap{background:var(--bg-3);height:10px;border-radius:4px;overflow:hidden}
.stage-bar-fill{height:100%;background:linear-gradient(90deg,var(--accent2),var(--purple));transition:width 1.2s}
.stage-val{font-family:'JetBrains Mono',monospace;font-weight:700;color:var(--gold);text-align:right;font-size:11px}
.accounts-wrap{display:flex;flex-direction:column;gap:5px}
.acc-row{display:grid;grid-template-columns:1fr 110px 60px;gap:10px;padding:7px 10px;background:var(--bg-2);border-radius:6px;font-size:11px;align-items:center;border-left:2px solid var(--accent2)}
.acc-row:hover{background:var(--bg-3)}
.acc-name{font-weight:600;color:var(--text)}
.acc-name .step{display:block;font-size:10px;color:var(--dim);font-weight:400;margin-top:2px}
.acc-stage{font-size:10px;color:var(--dim);font-family:'JetBrains Mono',monospace}
.acc-val{font-family:'JetBrains Mono',monospace;font-weight:700;color:var(--gold);text-align:right}
/* Risk matrix 5x5 */
.rm-wrap{display:grid;grid-template-columns:80px 1fr;gap:10px}
.rm-grid-5x5{display:grid;grid-template-columns:20px repeat(5,1fr);gap:3px}
.rm-cell{aspect-ratio:1.4;display:flex;align-items:center;justify-content:center;border-radius:4px;font-weight:800;font-size:14px;cursor:help;position:relative}
.rm-header{font-size:9px;color:var(--dim);text-align:center;display:flex;align-items:center;justify-content:center}
.rm-sev1{background:rgba(34,197,94,0.15);color:#86efac}
.rm-sev2{background:rgba(132,204,22,0.18);color:#d9f99d}
.rm-sev3{background:rgba(234,179,8,0.2);color:#fef08a}
.rm-sev4{background:rgba(249,115,22,0.22);color:#fed7aa}
.rm-sev5{background:rgba(239,68,68,0.3);color:#fca5a5}
.rm-sev-empty{background:var(--bg-3);color:var(--mute);font-weight:400;font-size:10px}
.risk-list{display:flex;flex-direction:column;gap:5px;margin-top:10px}
.risk-row{display:grid;grid-template-columns:40px 1fr auto;gap:8px;align-items:center;padding:6px 10px;background:var(--bg-2);border-radius:5px;font-size:11px;border-left:2px solid var(--warn)}
.risk-row.critical{border-left-color:var(--err)}
.risk-row.high{border-left-color:var(--warn)}
.risk-row.medium{border-left-color:var(--cyan)}
.risk-id{font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--mute)}
.risk-title{color:var(--text);font-weight:500}
.risk-title .mit{display:block;font-size:10px;color:var(--dim);font-style:italic;margin-top:2px}
.risk-score{font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:800;color:var(--text);text-align:right}
.loading{text-align:center;padding:50px;color:var(--dim)}
.spinner{width:38px;height:38px;border:3px solid var(--bg-3);border-top-color:var(--accent);border-radius:50%;margin:0 auto 14px;animation:spin 1s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
/* === OPUS RESPONSIVE FIX v2 19avr — append-only, doctrine #14 === */
@media(max-width: 480px) {
html, body { overflow-x: hidden !important; max-width: 100vw; }
body, main, section, article { word-break: break-word; overflow-wrap: anywhere; }
img, video, iframe, canvas, svg, table, pre, code { max-width: 100% !important; }
pre, code { white-space: pre-wrap; word-break: break-all; }
table { display: block; overflow-x: auto; }
.container, [class*="container"], [class*="wrapper"] { max-width: 100vw !important; padding-left: 12px !important; padding-right: 12px !important; }
[class*="grid"], [class*="-grid"] { grid-template-columns: 1fr !important; gap: 10px !important; }
[class*="kpi"], [class*="stats"], [class*="-cards"] { grid-template-columns: 1fr !important; }
header, nav, footer { flex-wrap: wrap !important; }
header > *, nav > *, footer > * { max-width: 100%; }
h1 { font-size: 22px !important; word-break: break-word; }
h2 { font-size: 18px !important; }
.pitch, [class*="pitch"], [class*="hero"] { word-break: break-word; overflow-wrap: anywhere; }
}
/* === OPUS RESPONSIVE FIX v2 END === */
</style>
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-141842 -->
<style id="doctrine60-ux-direct">
/* DOCTRINE-60-UX-ENRICH injected-direct */
body::before {
content: '';
position: fixed;
top: 0; left: 0; width: 100vw; height: 100vh;
background: radial-gradient(circle at 50% 50%, rgba(100,180,255,0.08), transparent 60%);
pointer-events: none;
z-index: -1;
}
.card, .kpi, .panel, .btn {
transition: all 0.3s cubic-bezier(0.2,0,0.1,1);
}
.card:hover, .kpi:hover, .panel:hover {
box-shadow: 0 4px 20px rgba(100,180,255,0.2);
border-color: rgba(100,180,255,0.5);
}
@keyframes pulseD60 {
0%,100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(1.05); }
}
.pulse, .live-indicator, .active, .online {
animation: pulseD60 3s ease-in-out infinite;
}
.modal, .chat, .speech, .overlay {
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.enter-stagger {
animation: enterStagD60 0.5s cubic-bezier(0.2,0,0.1,1) forwards;
}
@keyframes enterStagD60 {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<div class="container">
<header>
<div>
<h1><span>🎖️</span>DG Command Center <span class="pulse"></span></h1>
<div class="sub">Real-time pilotage — TOC · Conversion · Data · Marketing · CRM · Risk · Alertes</div>
<div class="clock" id="clock"></div>
</div>
<div class="actions">
<a href="/weval-technology-platform.html" class="btn">🏠 WTP</a>
<a href="/agent-roi-simulator.html" class="btn">🧮 ROI Sim</a>
<a href="/crm.html" class="btn">💼 CRM</a>
<button class="btn" id="btn-refresh" onclick="load()">↻ Refresh</button>
</div>
</header>
<!-- ALERTS DG TOP -->
<div class="card" style="margin-bottom:20px;border-left:4px solid var(--err)">
<div class="card-head">
<div class="card-title">🚨 Alertes DG — à traiter maintenant <span class="card-badge danger" id="alerts-count">— alertes</span></div>
<div class="card-badge" id="alerts-critical"></div>
</div>
<div class="alerts-strip" id="alerts-strip"><div class="loading"><div class="spinner"></div></div></div>
</div>
<!-- ROW: TOC + Conversion Funnel -->
<div class="row row-2">
<div class="card" id="toc-card">
<div class="card-head">
<div class="card-title">🎯 TOC Theory of Constraints — Goldratt</div>
<div class="card-badge danger" id="toc-bot-badge">— bottleneck</div>
</div>
<div class="toc-wrap" id="toc-streams"><div class="loading"><div class="spinner"></div></div></div>
<div style="margin-top:12px;padding:10px 12px;background:var(--bg-2);border-radius:6px;font-size:10.5px;color:var(--dim);line-height:1.5">
<strong style="color:var(--accent)">5 Focusing Steps (Goldratt):</strong>
1. Identifier la contrainte · 2. Exploiter (max) · 3. Subordonner tout le reste · 4. Élever la contrainte · 5. Si brisée → reprendre au 1
</div>
</div>
<div class="card">
<div class="card-head">
<div class="card-title">🎚️ Conversion Funnel</div>
<div class="card-badge info" id="conv-overall">— %</div>
</div>
<div class="funnel-wrap" id="funnel-wrap"><div class="loading"><div class="spinner"></div></div></div>
</div>
</div>
<!-- ROW: Data pipelines + Marketing KPIs -->
<div class="row row-2e">
<div class="card">
<div class="card-head"><div class="card-title">🔌 Data Pipelines Health</div><div class="card-badge" id="dp-badge">live</div></div>
<div class="dp-wrap" id="dp-wrap"><div class="loading"><div class="spinner"></div></div></div>
</div>
<div class="card">
<div class="card-head"><div class="card-title">📣 Marketing KPIs</div><div class="card-badge info">WEVADS + Ethica</div></div>
<div class="mkt-grid" id="mkt-grid"><div class="loading"><div class="spinner"></div></div></div>
</div>
</div>
<!-- ROW: CRM pipeline + Top accounts -->
<div class="row row-2e">
<div class="card">
<div class="card-head">
<div class="card-title">💼 CRM Pipeline by Stage</div>
<div class="card-badge info" id="pipe-val">— k€</div>
</div>
<div id="crm-stages"><div class="loading"><div class="spinner"></div></div></div>
<div style="margin-top:12px;padding-top:10px;border-top:1px dashed var(--border);display:grid;grid-template-columns:repeat(4,1fr);gap:8px;font-size:10.5px;color:var(--dim)">
<div><strong style="color:var(--text);display:block;font-size:14px;font-family:'JetBrains Mono',monospace" id="crm-opps"></strong>Opps actives</div>
<div><strong style="color:var(--ok);display:block;font-size:14px;font-family:'JetBrains Mono',monospace" id="crm-won"></strong>Won ce mois</div>
<div><strong style="color:var(--err);display:block;font-size:14px;font-family:'JetBrains Mono',monospace" id="crm-lost"></strong>Lost</div>
<div><strong style="color:var(--purple);display:block;font-size:14px;font-family:'JetBrains Mono',monospace" id="crm-cycle"></strong>Cycle (j)</div>
</div>
</div>
<div class="card">
<div class="card-head"><div class="card-title">🎯 Top Accounts &amp; Next Steps</div><div class="card-badge info" id="acc-badge"></div></div>
<div class="accounts-wrap" id="accounts-wrap"><div class="loading"><div class="spinner"></div></div></div>
</div>
</div>
<!-- ROW: Risk Management 5x5 + Risk list -->
<div class="row row-2e">
<div class="card">
<div class="card-head">
<div class="card-title">⚠️ Risk Management WEVAL — Matrice 5×5</div>
<div class="card-badge danger" id="risk-count"></div>
</div>
<div class="rm-wrap">
<div style="display:flex;flex-direction:column;justify-content:space-around;font-size:10px;color:var(--dim);text-align:right;font-weight:600">
<div>Likelihood</div>
<div>L=5</div><div>L=4</div><div>L=3</div><div>L=2</div><div>L=1</div>
</div>
<div>
<div class="rm-grid-5x5" id="risk-matrix"></div>
<div style="display:grid;grid-template-columns:20px repeat(5,1fr);gap:3px;margin-top:4px">
<div></div>
<div class="rm-header">Impact 1</div><div class="rm-header">2</div><div class="rm-header">3</div><div class="rm-header">4</div><div class="rm-header">5</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-head"><div class="card-title">📋 Top 8 Risques à traiter</div><div class="card-badge danger" id="risks-prio"></div></div>
<div class="risk-list" id="risk-list"><div class="loading"><div class="spinner"></div></div></div>
</div>
</div>
</div>
<script>
function formatK(n){if(n==null)return '';if(n>=1000000)return (n/1000000).toFixed(1).replace('.0','')+'M';if(n>=1000)return (n/1000).toFixed(1).replace('.0','')+'K';return n.toLocaleString();}
const API = '/api/wevia-v69-dg-command-center.php';
let DATA = null;
function clockTick(){
const d = new Date();
document.getElementById('clock').textContent = d.toLocaleDateString('fr-FR') + ' · ' + d.toLocaleTimeString('fr-FR') + ' · auto-refresh 20s';
}
setInterval(clockTick, 1000); clockTick();
async function load(){
try {
const r = await fetch(API + '?t=' + Date.now());
DATA = await r.json();
render();
} catch(e) { console.error(e); }
}
function fmt(n){
if (!n && n !== 0) return '—';
if (Math.abs(n) >= 1000000) return (n/1000000).toFixed(2)+'M';
if (Math.abs(n) >= 1000) return (n/1000).toFixed(1)+'k';
return Math.round(n);
}
function render(){
if (!DATA) return;
const s = DATA.summary;
// Alerts
const alerts = DATA.alerts_dg || [];
document.getElementById('alerts-count').textContent = alerts.length + ' alertes';
document.getElementById('alerts-critical').textContent = s.alerts_critical + ' critical';
document.getElementById('alerts-critical').className = 'card-badge ' + (s.alerts_critical > 0 ? 'danger' : 'info');
document.getElementById('alerts-strip').innerHTML = alerts.map(a => `
<div class="alert-card ${a.level}">
<div class="alert-head">
<div class="alert-title"><span>${a.icon}</span>${a.title}</div>
<div class="alert-lvl ${a.level}">${a.level}</div>
</div>
<div class="alert-detail">${a.detail}</div>
<div class="alert-foot">
<span class="deadline">⏱ ${a.deadline}</span>
<a href="${a.action_link}">→ Action</a>
</div>
</div>
`).join('');
// TOC
const streams = (DATA.toc && DATA.toc.streams) || [];
document.getElementById('toc-bot-badge').textContent = '🔴 ' + (s.toc_bottleneck_label || '—');
document.getElementById('toc-streams').innerHTML = streams.map(st => {
const isBot = st.id === DATA.toc.bottleneck;
const pct = Math.min(100, st.utilization_pct);
return `<div class="toc-stream ${st.status} ${isBot?'bottleneck':''}">
<div class="toc-icon">${st.icon}</div>
<div class="toc-label">${st.label}${isBot?' <span class="card-badge danger" style="margin-left:4px">GOULET</span>':''}<div class="small">${st.constraint}</div></div>
<div class="toc-throughput">${st.throughput}<div style="font-size:9px;color:var(--mute);font-weight:400">${st.unit}</div></div>
<div><div class="toc-bar-wrap"><div class="toc-bar-fill ${isBot?'bot':''}" style="width:0%" data-w="${pct}"></div></div></div>
<div class="toc-util">${pct.toFixed(0)}%<div style="font-size:9px;color:var(--mute)">cap ${st.capacity}</div></div>
</div>`;
}).join('');
setTimeout(()=>document.querySelectorAll('.toc-bar-fill').forEach(el=>el.style.width=el.dataset.w+'%'), 80);
// Funnel
const funnel = DATA.conversion_funnel || [];
document.getElementById('conv-overall').textContent = s.conversion_overall_pct.toFixed(3) + '% overall';
const maxCount = Math.max(...funnel.map(f=>f.count), 1);
document.getElementById('funnel-wrap').innerHTML = funnel.map((f,i) => {
const w = (f.count/maxCount)*100;
const cls = (f.conv_pct||100) < 15 ? 'danger' : (f.conv_pct||100) < 35 ? 'warn' : '';
return `<div class="funnel-row">
<div class="funnel-label">${f.step}</div>
<div class="funnel-bar-wrap"><div class="funnel-bar" style="width:0%;background:${f.color}" data-w="${w}">${formatK(f.count)}</div></div>
<div class="funnel-count">${fmt(f.count)}</div>
<div class="funnel-conv ${cls}">${f.conv_pct||100}%</div>
</div>`;
}).join('');
setTimeout(()=>document.querySelectorAll('.funnel-bar').forEach(el=>el.style.width=el.dataset.w+'%'), 100);
// Data pipelines
const dp = DATA.data_pipeline || [];
document.getElementById('dp-wrap').innerHTML = dp.map(d => {
const pct = d.target ? Math.min(100, (d.volume/d.target)*100) : 100;
return `<div class="dp-row">
<div class="dp-name">${d.name}</div>
<div class="dp-vol">${fmt(d.volume)} ${d.unit||''}</div>
<div class="dp-bar-wrap"><div class="dp-bar-fill" style="width:0%" data-w="${pct}"></div></div>
<div class="dp-status ${d.status}">${d.status}</div>
</div>`;
}).join('');
setTimeout(()=>document.querySelectorAll('.dp-bar-fill').forEach(el=>el.style.width=el.dataset.w+'%'), 120);
// Marketing
const m = DATA.marketing || {};
const mktCells = [
{l:'HCPs Maghreb', v:fmt(m.ethica_hcps), u:''},
{l:'Emails valides', v:fmt(m.emails_validated), u:''},
{l:'Warmup accts', v:fmt(m.warmup_accounts), u:''},
{l:'Seeds actifs', v:fmt(m.seed_accounts), u:''},
{l:'Inbox rate', v:m.inbox_rate_pct, u:'%'},
{l:'Open rate', v:m.open_rate_pct, u:'%'},
{l:'Click rate', v:m.click_rate_pct, u:'%'},
{l:'Conversions', v:m.conversions_month, u:'/mois'},
{l:'CAC', v:m.cac_eur, u:'€'},
{l:'LTV', v:m.ltv_eur, u:'€'},
{l:'Deliver. mean wk', v:m.email_deliverability_mean_week, u:'%'},
{l:'Campaigns live', v:2, u:''}
];
document.getElementById('mkt-grid').innerHTML = mktCells.map(c => `<div class="mkt-cell"><div class="l">${c.l}</div><div class="v">${c.v}<span class="u">${c.u}</span></div></div>`).join('');
// CRM pipeline by stage
const crm = DATA.crm || {};
const stages = crm.pipeline_by_stage || [];
document.getElementById('pipe-val').textContent = fmt(crm.pipeline_value_keur) + ' k€';
document.getElementById('crm-opps').textContent = crm.opportunities_active;
document.getElementById('crm-won').textContent = crm.deals_won_month;
document.getElementById('crm-lost').textContent = crm.deals_lost_month;
document.getElementById('crm-cycle').textContent = crm.avg_cycle_days;
const maxVal = Math.max(...stages.map(s=>s.value_keur), 1);
document.getElementById('crm-stages').innerHTML = stages.map(st => {
const w = (st.value_keur/maxVal)*100;
return `<div class="crm-stage-row">
<div class="stage-label">${st.stage}</div>
<div class="stage-count">${formatK(st.count)}</div>
<div><div class="stage-bar-wrap"><div class="stage-bar-fill" style="width:0%" data-w="${w}"></div></div></div>
<div class="stage-val">${fmt(st.value_keur)} k€</div>
</div>`;
}).join('');
setTimeout(()=>document.querySelectorAll('.stage-bar-fill').forEach(el=>el.style.width=el.dataset.w+'%'), 130);
// Top accounts
const accs = crm.top_accounts || [];
document.getElementById('acc-badge').textContent = accs.length + ' accounts';
document.getElementById('accounts-wrap').innerHTML = accs.map(a => `
<div class="acc-row">
<div class="acc-name">${a.name}<span class="step">→ ${a.next_step}</span></div>
<div class="acc-stage">${a.stage}</div>
<div class="acc-val">${a.value_keur ? fmt(a.value_keur)+'k€' : '—'}</div>
</div>
`).join('');
// Risk matrix 5x5
const risks = DATA.risks || [];
document.getElementById('risk-count').textContent = s.risks_critical + ' critical · ' + s.risks_high + ' high';
const grid = {};
risks.forEach(r => { const k=r.likelihood+'_'+r.impact; grid[k]=(grid[k]||[]); grid[k].push(r); });
let rmHtml = '<div></div>'; // corner top-left
for (let imp=1; imp<=5; imp++) rmHtml += `<div class="rm-header" style="height:14px"></div>`; // column headers actually placed below
// Rows L=5 → L=1 (high likelihood at top)
rmHtml = '';
for (let l=5; l>=1; l--) {
rmHtml += `<div class="rm-header" style="font-size:10px">L=${l}</div>`;
for (let i=1; i<=5; i++) {
const cells = grid[l+'_'+i] || [];
const sev = l*i;
let cls = 'rm-sev-empty';
if (sev >= 20) cls = 'rm-sev5';
else if (sev >= 15) cls = 'rm-sev5';
else if (sev >= 10) cls = 'rm-sev4';
else if (sev >= 6) cls = 'rm-sev3';
else if (sev >= 3) cls = 'rm-sev2';
else cls = 'rm-sev1';
rmHtml += `<div class="rm-cell ${cls}" title="${cells.map(c=>c.id+': '+c.title).join(' · ')}">${cells.length || '·'}</div>`;
}
}
document.getElementById('risk-matrix').innerHTML = rmHtml;
// Risk list (top 8 by severity)
const sorted = [...risks].sort((a,b) => (b.likelihood*b.impact) - (a.likelihood*a.impact)).slice(0, 8);
document.getElementById('risks-prio').textContent = risks.length + ' risques total';
document.getElementById('risk-list').innerHTML = sorted.map(r => `
<div class="risk-row ${r.priority}">
<div class="risk-id">${r.id}</div>
<div class="risk-title">${r.title}<span class="mit">🛡 ${r.mitigation}</span></div>
<div class="risk-score">${r.likelihood}×${r.impact}=<strong>${r.likelihood*r.impact}</strong></div>
</div>
`).join('');
}
load();
setInterval(load, 20000);
</script>
<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) {
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 (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);} });
}
}
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/archi-meta-badge.js" defer></script>
<script src="/api/a11y-auto-enhancer.js" defer></script>
<!-- WTP_UDOCK_V1 (Opus 21-avr t32b4) --><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>