Files
html/erp-gap-fill-offer.html

726 lines
35 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 · ERP Gap-Fill Agents — Offre commerciale</title>
<style>
:root {
--bg-0:#05060a; --bg-1:#0b0d15; --bg-2:#11141f; --bg-3:#171b2a;
--border:rgba(99,102,241,0.15); --border-hover:rgba(99,102,241,0.35);
--text:#e2e8f0; --text-dim:#94a3b8; --text-mute:#64748b;
--accent:#14b8a6; --accent-2:#6366f1; --purple:#a855f7; --cyan:#06b6d4;
--ok:#22c55e; --warn:#f59e0b; --err:#ef4444; --rose:#f43f5e; --amber:#f59e0b;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background: radial-gradient(ellipse at top, #0f1420 0%, #05060a 60%);
color: var(--text);
min-height: 100vh;
font-size: 13.5px;
line-height: 1.55;
}
.container { max-width: 1600px; margin: 0 auto; padding: 32px 36px 80px; }
/* HEADER */
header {
display: flex; justify-content: space-between; align-items: flex-start;
margin-bottom: 32px; padding-bottom: 24px;
border-bottom: 1px solid var(--border);
}
header h1 {
font-size: 30px; font-weight: 800; line-height: 1.1;
background: linear-gradient(90deg, #22d3ee, #a855f7, #f43f5e);
-webkit-background-clip: text; background-clip: text; color: transparent;
letter-spacing: -0.5px;
}
header .sub { color: var(--text-dim); font-size: 14px; margin-top: 8px; max-width: 780px; line-height: 1.55; }
header .pitch {
margin-top: 14px; padding: 12px 18px;
background: linear-gradient(135deg, rgba(20,184,166,0.08), rgba(168,85,247,0.06));
border-left: 3px solid var(--accent);
border-radius: 6px; font-size: 13px; color: var(--text);
}
header .actions { display: flex; gap: 10px; }
.btn {
padding: 9px 16px; background: var(--bg-2); border: 1px solid var(--border);
color: var(--text); border-radius: 8px; cursor: pointer; font-size: 12.5px;
font-family: inherit; text-decoration: none; transition: all .2s;
display: inline-flex; align-items: center; gap: 6px;
}
.btn:hover { border-color: var(--accent); color: var(--accent); }
.btn-primary { background: linear-gradient(135deg, var(--accent-2), var(--purple)); border: none; color: white; }
.pulse {
display: inline-block; width: 7px; height: 7px; border-radius: 50%;
background: var(--ok); box-shadow: 0 0 0 0 rgba(34,197,94,0.7);
animation: pulse 2s infinite; margin-right: 4px;
}
@keyframes pulse { 0%{box-shadow:0 0 0 0 rgba(34,197,94,0.7)} 70%{box-shadow:0 0 0 8px rgba(34,197,94,0)} 100%{box-shadow:0 0 0 0 rgba(34,197,94,0)} }
/* KPI summary strip */
.kpi-strip {
display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px;
margin-bottom: 32px;
}
.kpi {
background: var(--bg-1); border: 1px solid var(--border); border-radius: 12px;
padding: 18px; position: relative; overflow: hidden;
transition: all .2s;
}
.kpi:hover { border-color: var(--accent); transform: translateY(-2px); }
.kpi::before { content: ''; position: absolute; left: 0; top: 0; width: 3px; height: 100%; background: var(--accent); }
.kpi.c2::before { background: var(--purple); }
.kpi.c3::before { background: var(--rose); }
.kpi.c4::before { background: var(--cyan); }
.kpi.c5::before { background: var(--amber); }
.kpi .label { color: var(--text-dim); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.7px; margin-bottom: 6px; font-weight: 600; }
.kpi .value { font-size: 26px; font-weight: 800; color: var(--text); letter-spacing: -0.4px; line-height: 1; }
.kpi .unit { font-size: 13px; color: var(--text-dim); margin-left: 4px; font-weight: 500; }
.kpi .trend { color: var(--text-dim); font-size: 10.5px; margin-top: 6px; }
/* Section title */
.section-title {
font-size: 18px; font-weight: 700; color: var(--text);
margin: 40px 0 16px; display: flex; align-items: center; gap: 10px;
letter-spacing: -0.3px;
}
.section-title::before {
content: ''; width: 4px; height: 22px;
background: linear-gradient(180deg, var(--accent), var(--purple)); border-radius: 2px;
}
.section-title .suffix { color: var(--text-dim); font-size: 12.5px; font-weight: 500; margin-left: 4px; }
/* Cards */
.card {
background: var(--bg-1); border: 1px solid var(--border);
border-radius: 14px; padding: 22px; margin-bottom: 18px;
}
.card h4 { font-size: 15px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
.card .desc { color: var(--text-dim); font-size: 12.5px; margin-bottom: 18px; }
/* =================== RISK MATRIX 5x5 =================== */
.risk-grid {
display: grid; grid-template-columns: 220px 1fr; gap: 20px;
}
.risk-matrix {
display: grid; grid-template-columns: 60px repeat(5, 1fr); grid-template-rows: repeat(6, 62px);
gap: 4px;
}
.rm-header {
display: flex; align-items: center; justify-content: center;
font-size: 11px; color: var(--text-dim); font-weight: 600;
text-transform: uppercase; letter-spacing: 0.5px;
}
.rm-axis-y { transform: rotate(-90deg); white-space: nowrap; }
.rm-cell {
border-radius: 8px; position: relative; display: flex; align-items: center; justify-content: center;
font-size: 22px; font-weight: 800; cursor: pointer; transition: all .2s;
border: 1px solid rgba(255,255,255,0.04);
}
.rm-cell:hover { transform: scale(1.05); z-index: 5; box-shadow: 0 8px 24px rgba(0,0,0,0.5); border-color: var(--text); }
/* Severity color grid (i*j) */
.rm-sev-1 { background: rgba(34,197,94,0.15); color: #86efac; }
.rm-sev-2 { background: rgba(132,204,22,0.18); color: #d9f99d; }
.rm-sev-3 { background: rgba(234,179,8,0.18); color: #fef08a; }
.rm-sev-4 { background: rgba(249,115,22,0.22); color: #fed7aa; }
.rm-sev-5 { background: rgba(239,68,68,0.25); color: #fca5a5; }
.rm-sev-6 { background: rgba(239,68,68,0.35); color: #fca5a5; }
.rm-sev-empty { background: var(--bg-2); color: var(--text-mute); font-size: 11px; font-weight: 400; }
.rm-cell .tip {
position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%);
background: var(--bg-3); border: 1px solid var(--border); padding: 8px 12px;
border-radius: 8px; font-size: 11px; font-weight: normal; color: var(--text);
white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity .2s;
z-index: 10; margin-bottom: 6px;
}
.rm-cell:hover .tip { opacity: 1; }
.risk-legend {
display: flex; flex-direction: column; gap: 10px;
}
.risk-legend h5 { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.6px; margin-bottom: 6px; }
.risk-legend-row { display: flex; align-items: center; gap: 8px; font-size: 12px; }
.risk-legend-sq { width: 16px; height: 16px; border-radius: 3px; }
/* Risk list below */
.risk-list {
display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 10px;
margin-top: 20px;
}
.risk-item {
background: var(--bg-2); border: 1px solid var(--border); border-radius: 8px;
padding: 10px 12px; font-size: 12px; transition: all .2s;
border-left: 3px solid var(--warn);
}
.risk-item.critical { border-left-color: var(--err); }
.risk-item.high { border-left-color: var(--warn); }
.risk-item.medium { border-left-color: var(--cyan); }
.risk-item:hover { background: var(--bg-3); }
.risk-item .rid { font-family: 'JetBrains Mono', monospace; color: var(--accent); font-weight: 600; font-size: 11px; }
.risk-item .rtitle { color: var(--text); font-weight: 500; margin-top: 3px; line-height: 1.35; }
.risk-item .rmeta { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
.risk-item .rmeta span {
font-size: 10px; padding: 1px 6px; border-radius: 4px;
background: rgba(99,102,241,0.12); color: var(--text-dim);
}
.risk-item .rmeta .ag { background: rgba(20,184,166,0.15); color: var(--accent); }
/* ERP GAPS table */
.erp-gap-grid {
display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px;
}
@media(max-width: 1280px) { .erp-gap-grid { grid-template-columns: repeat(2, 1fr); } }
@media(max-width: 768px) { .erp-gap-grid { grid-template-columns: 1fr; } }
.erp-dept {
background: var(--bg-1); border: 1px solid var(--border);
border-radius: 12px; padding: 18px;
transition: all .2s;
}
.erp-dept:hover { border-color: var(--border-hover); }
.erp-dept h4 {
font-size: 15px; font-weight: 700; color: var(--text); margin-bottom: 10px;
display: flex; align-items: center; gap: 8px;
}
.erp-native {
display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px;
padding-bottom: 12px; border-bottom: 1px dashed var(--border);
}
.erp-native span {
font-size: 10px; padding: 2px 6px; border-radius: 4px;
font-family: 'JetBrains Mono', monospace;
}
.erp-native .sap { background: rgba(14,165,233,0.15); color: #7dd3fc; }
.erp-native .oracle { background: rgba(239,68,68,0.13); color: #fca5a5; }
.erp-native .sage { background: rgba(34,197,94,0.13); color: #86efac; }
.erp-gap-row {
padding: 10px 0; border-bottom: 1px solid var(--bg-3);
}
.erp-gap-row:last-child { border: none; }
.erp-gap-title {
font-size: 12.5px; color: var(--text); font-weight: 500; margin-bottom: 4px;
display: flex; align-items: flex-start; gap: 6px;
}
.erp-gap-title::before { content: '🚧'; flex-shrink: 0; }
.erp-gap-agent {
font-size: 11px; color: var(--accent); margin-top: 4px;
font-family: 'JetBrains Mono', monospace; font-weight: 600;
}
.erp-gap-cant {
font-size: 10.5px; color: var(--text-mute); margin-top: 2px; font-style: italic;
}
.erp-gap-verts {
display: flex; gap: 4px; margin-top: 6px; flex-wrap: wrap;
}
.erp-gap-verts span {
font-size: 9.5px; padding: 1px 6px; border-radius: 8px;
background: rgba(168,85,247,0.15); color: #d4a7fa; font-weight: 500;
}
/* VERTICALS */
.vert-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px;
}
.vert {
background: var(--bg-1); border: 1px solid var(--border);
border-radius: 12px; padding: 18px; position: relative;
border-left: 4px solid var(--vcol, var(--accent));
transition: all .2s;
}
.vert:hover { transform: translateY(-2px); box-shadow: 0 12px 32px rgba(0,0,0,0.35); }
.vert h4 { font-size: 15px; font-weight: 700; color: var(--text); margin-bottom: 6px; }
.vert .erp { font-size: 11px; color: var(--text-dim); margin-bottom: 12px; font-family: 'JetBrains Mono', monospace; }
.vert .stats { display: flex; gap: 14px; margin: 12px 0; }
.vert .stat { flex: 1; }
.vert .stat-v { font-size: 20px; font-weight: 800; color: var(--text); line-height: 1; }
.vert .stat-l { font-size: 10px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.4px; margin-top: 3px; }
.vert-gaps {
background: var(--bg-2); border-radius: 8px; padding: 10px; font-size: 11px;
color: var(--text-dim); line-height: 1.5;
}
.vert-gaps strong { color: var(--accent); display: block; margin-bottom: 4px; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.5px; }
.vert-clients {
display: flex; gap: 5px; flex-wrap: wrap; margin-top: 10px;
}
.vert-clients span {
font-size: 10px; padding: 2px 7px; border-radius: 10px;
background: rgba(99,102,241,0.13); color: #c7d2fe; font-weight: 500;
}
/* SERVICES pricing */
.services-grid {
display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px;
}
@media(max-width: 900px){ .services-grid { grid-template-columns: repeat(2, 1fr); } }
.service {
background: var(--bg-1); border: 1px solid var(--border);
border-radius: 12px; padding: 20px; text-align: center;
transition: all .2s;
}
.service:hover { border-color: var(--accent); transform: translateY(-2px); }
.service h5 { font-size: 14px; font-weight: 700; color: var(--text); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.4px; }
.service .duration { font-size: 11px; color: var(--text-dim); margin-bottom: 12px; }
.service .price { font-size: 22px; font-weight: 800; background: linear-gradient(135deg, var(--accent), var(--purple)); -webkit-background-clip: text; background-clip: text; color: transparent; margin: 12px 0; }
.service .deliverable { font-size: 11.5px; color: var(--text-dim); line-height: 1.5; padding-top: 12px; border-top: 1px solid var(--bg-3); }
/* Pilot KPIs */
.pilot-kpi-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.pilot-kpi-card { background: var(--bg-1); border: 1px solid var(--border); border-radius: 12px; padding: 18px; }
.pilot-kpi-card h5 { font-size: 13px; color: var(--text); margin-bottom: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
.pilot-kpi-list { display: flex; flex-direction: column; gap: 10px; }
.pilot-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; background: var(--bg-2); border-radius: 6px; }
.pilot-row .l { font-size: 12px; color: var(--text); }
.pilot-row .v { font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 13px; color: var(--text); }
.pilot-row.ok { border-left: 3px solid var(--ok); }
.pilot-row.warn { border-left: 3px solid var(--warn); }
.pilot-row.critical { border-left: 3px solid var(--err); }
.pilot-row.critical .v { color: #fca5a5; }
.loading { text-align: center; padding: 80px 20px; color: var(--text-dim); }
.spinner { width: 42px; height: 42px; border: 3px solid var(--bg-3); border-top-color: var(--accent); border-radius: 50%; margin: 0 auto 18px; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media(max-width: 1024px) {
.kpi-strip { grid-template-columns: repeat(2, 1fr); }
.risk-grid { grid-template-columns: 1fr; }
.pilot-kpi-grid { grid-template-columns: 1fr; }
}
/* === 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>
</head>
<body>
<div class="container" id="app">
<header>
<div>
<h1>🎯 WEVAL ERP Gap-Fill Agents <span class="pulse"></span></h1>
<div class="sub">Offre service commerciale — Agents AI autonomes pour combler les gaps fonctionnels des ERPs SAP/Oracle/Sage, par département métier, adaptables à 7 verticaux industriels.</div>
<div class="pitch" id="pitch-text">Les ERPs traditionnels ne couvrent pas 100% des besoins métier. WEVAL déploie des agents IA autonomes pour combler les gaps là où SAP, Oracle, Sage échouent.</div>
</div>
<div class="actions">
<a href="/weval-technology-platform.html" class="btn">← WTP Portal</a>
<button class="btn btn-primary" onclick="window.print()">📑 Print offre</button>
</div>
</header>
<!-- KPI SUMMARY STRIP -->
<div class="kpi-strip" id="kpi-strip">
<div class="kpi"><div class="label">Risques catalogués</div><div class="value" id="k-risks"></div><div class="trend" id="k-risks-sub">chargement…</div></div>
<div class="kpi c2"><div class="label">ERP Gaps identifiés</div><div class="value" id="k-gaps"></div><div class="trend" id="k-gaps-sub">SAP · Oracle · Sage</div></div>
<div class="kpi c3"><div class="label">Verticaux couverts</div><div class="value" id="k-verts"></div><div class="trend">Retail · Pharma · Banque · Industrie · Services · Conseil · Énergie</div></div>
<div class="kpi c4"><div class="label">Agents pack total</div><div class="value" id="k-pack"></div><div class="trend" id="k-pack-sub">tous verticaux confondus</div></div>
<div class="kpi c5"><div class="label">TAM pipeline</div><div class="value" id="k-tam"><span class="unit">M€</span></div><div class="trend" id="k-tam-sub">sur clients identifiés</div></div>
</div>
<!-- RISK MATRIX -->
<div class="section-title">🎲 Matrice des risques 5×5 <span class="suffix">— Likelihood × Impact, mitigés par agents WEVAL</span></div>
<div class="card">
<div class="risk-grid">
<div class="risk-legend">
<h5>Légende sévérité</h5>
<div class="risk-legend-row"><div class="risk-legend-sq" style="background:rgba(34,197,94,0.15)"></div>Faible (1-4)</div>
<div class="risk-legend-row"><div class="risk-legend-sq" style="background:rgba(234,179,8,0.18)"></div>Modéré (5-9)</div>
<div class="risk-legend-row"><div class="risk-legend-sq" style="background:rgba(249,115,22,0.22)"></div>Élevé (10-14)</div>
<div class="risk-legend-row"><div class="risk-legend-sq" style="background:rgba(239,68,68,0.35)"></div>Critique (15-25)</div>
<div style="margin-top:18px;font-size:11px;color:var(--text-mute);line-height:1.5">Chaque cellule = nombre de risques. Cliquez pour filtrer la liste ci-dessous.</div>
</div>
<div class="risk-matrix" id="risk-matrix">
<!-- filled by JS -->
</div>
</div>
<div class="risk-list" id="risk-list">
<div class="loading"><div class="spinner"></div>Chargement des risques…</div>
</div>
</div>
<!-- PILOT KPIs -->
<div class="section-title">📊 KPIs pilotage offre <span class="suffix">— Commercial · Delivery · Risk</span></div>
<div class="pilot-kpi-grid" id="pilot-kpis"><div class="loading"><div class="spinner"></div></div></div>
<!-- ERP GAP CATALOG -->
<div class="section-title">🔧 ERP Gap-Fill Catalog <span class="suffix">— 7 départements × SAP/Oracle/Sage</span></div>
<div class="erp-gap-grid" id="erp-gap-grid"><div class="loading"><div class="spinner"></div></div></div>
<!-- VERTICALS -->
<div class="section-title">🏭 7 Verticaux clients <span class="suffix">— Pipeline adapté par industrie</span></div>
<div class="vert-grid" id="vert-grid"><div class="loading"><div class="spinner"></div></div></div>
<!-- SERVICES -->
<div class="section-title">💼 Services offerts <span class="suffix">— Go-to-market pricing</span></div>
<div class="services-grid" id="services-grid"><div class="loading"><div class="spinner"></div></div></div>
</div>
<script>
const API = '/api/wevia-v65-risk-erp-gaps.php';
let DATA = null;
async function load(){
try {
const r = await fetch(API + '?t=' + Date.now());
DATA = await r.json();
render();
} catch(e) {
console.error(e);
document.getElementById('k-risks-sub').textContent = 'Erreur API';
}
}
function render(){
if (!DATA) return;
const s = DATA.summary;
// Pitch
if (DATA.pitch) document.getElementById('pitch-text').textContent = '💡 ' + DATA.pitch;
// KPI strip
document.getElementById('k-risks').textContent = s.risks_total;
document.getElementById('k-risks-sub').textContent = s.risks_critical + ' critical · ' + s.risks_high + ' high';
document.getElementById('k-gaps').textContent = s.erp_gaps_total;
document.getElementById('k-gaps-sub').textContent = s.erp_departments + ' départements';
document.getElementById('k-verts').textContent = s.verticals;
document.getElementById('k-pack').textContent = s.agents_pack_total;
document.getElementById('k-pack-sub').textContent = s.clients_pipeline_total + ' clients identifiés';
document.getElementById('k-tam').innerHTML = s.tam_m_eur + '<span class="unit">M€</span>';
document.getElementById('k-tam-sub').textContent = s.clients_pipeline_total + ' clients × revenue avg';
renderRiskMatrix();
renderRiskList();
renderPilotKpis();
renderERPGaps();
renderVerticals();
renderServices();
}
function renderRiskMatrix(){
const wrap = document.getElementById('risk-matrix');
const risks = DATA.risks || [];
// Build 5x5 count matrix — axes: likelihood (y 5→1) × impact (x 1→5)
const grid = {};
risks.forEach(r => {
const k = r.likelihood + '_' + r.impact;
grid[k] = (grid[k] || []);
grid[k].push(r);
});
let html = '';
// Top: empty corner + impact labels 1→5
html += '<div></div>';
for (let imp = 1; imp <= 5; imp++){
html += '<div class="rm-header">Impact ' + imp + '</div>';
}
// Rows: likelihood 5→1 (high at top)
for (let like = 5; like >= 1; like--){
html += '<div class="rm-header rm-axis-y">L=' + like + '</div>';
for (let imp = 1; imp <= 5; imp++){
const cell = grid[like + '_' + imp] || [];
const sev = like * imp;
let sevClass = 'rm-sev-empty';
if (sev >= 20) sevClass = 'rm-sev-6';
else if (sev >= 15) sevClass = 'rm-sev-5';
else if (sev >= 10) sevClass = 'rm-sev-4';
else if (sev >= 6) sevClass = 'rm-sev-3';
else if (sev >= 3) sevClass = 'rm-sev-2';
else sevClass = 'rm-sev-1';
const count = cell.length;
const tipText = count ? cell.map(r => r.id + ': ' + r.title).join(' · ') : 'Aucun risque';
html += '<div class="rm-cell ' + sevClass + '" data-sev="' + sev + '" data-count="' + count + '" onclick="filterRisks(' + like + ',' + imp + ')">';
html += count || '·';
html += '<div class="tip">' + tipText.substring(0, 120) + (tipText.length > 120 ? '…' : '') + '</div>';
html += '</div>';
}
}
wrap.innerHTML = html;
}
function renderRiskList(filter){
const wrap = document.getElementById('risk-list');
let risks = DATA.risks || [];
if (filter) risks = risks.filter(r => r.likelihood === filter.l && r.impact === filter.i);
risks = risks.sort((a, b) => (b.likelihood * b.impact) - (a.likelihood * a.impact));
wrap.innerHTML = risks.map(r => `
<div class="risk-item ${r.priority}">
<div class="rid">${r.id} · L${r.likelihood}×I${r.impact}=${r.likelihood*r.impact}</div>
<div class="rtitle">${r.title}</div>
<div class="rmeta">
<span>${r.dept}</span>
<span>${r.erp}</span>
<span class="ag">🤖 ${r.mitigation_agent}</span>
</div>
</div>
`).join('');
}
function filterRisks(l, i){
renderRiskList({l, i});
// Scroll to list
document.getElementById('risk-list').scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function renderPilotKpis(){
const wrap = document.getElementById('pilot-kpis');
const pk = DATA.pilot_kpis || {};
const risks = DATA.risks || [];
const groups = [
{key: 'commercial', icon: '', label: 'Commercial'},
{key: 'delivery', icon: '', label: 'Delivery'},
{key: 'risk_pilotage', icon: '', label: 'Risk Pilotage'}
];
wrap.innerHTML = groups.map(g => {
const items = pk[g.key] || [];
let extra = '';
if(g.key === 'risk_pilotage' && risks.length){
const crit = risks.filter(r=>r.priority==='critical');
const high = risks.filter(r=>r.priority==='high');
const med = risks.filter(r=>r.priority==='medium');
const closed = risks.filter(r=>r.status==='closed').length;
const open = risks.filter(r=>r.status==='open').length;
const mon = risks.filter(r=>r.status==='monitored').length;
const renderRisk = (r, color, bg) => `<div style="padding:8px 10px;margin-bottom:6px;background:${bg};border-left:3px solid ${color};border-radius:6px;font-size:11.5px">
<div style="font-weight:700;color:${color}">${r.id} &bull; ${r.title}</div>
<div style="margin-top:3px;color:#94a3b8;font-size:10.5px">${r.dept} &bull; ${r.erp} &bull; Likelihood ${r.likelihood}/5 &bull; Impact ${r.impact}/5 &bull; <span style="color:${r.status==='closed'?'#22c55e':r.status==='monitored'?'#f59e0b':'#ef4444'};font-weight:700">${(r.status||'').toUpperCase()}</span></div>
<div style="margin-top:4px;color:#a5b4fc;font-size:10.5px">Agent mitigation: ${r.mitigation_agent}</div>
</div>`;
extra = `<div style="margin-top:14px;padding-top:12px;border-top:1px solid rgba(148,163,184,0.15)">
<div style="font-size:11px;font-weight:700;color:#fca5a5;margin-bottom:8px">RISQUES CRITIQUES (${crit.length})</div>
${crit.map(r=>renderRisk(r,'#fca5a5','rgba(239,68,68,0.08)')).join('')}
<div style="font-size:11px;font-weight:700;color:#fdba74;margin:12px 0 8px">RISQUES HAUTS (${high.length})</div>
${high.map(r=>renderRisk(r,'#fdba74','rgba(249,115,22,0.08)')).join('')}
${med.length?`<div style="font-size:11px;font-weight:700;color:#fde047;margin:12px 0 8px">RISQUES MOYENS (${med.length})</div>
${med.map(r=>renderRisk(r,'#fde047','rgba(234,179,8,0.06)')).join('')}`:''}
<div style="font-size:11px;font-weight:700;color:#6ee7b7;margin:12px 0 8px">STATUT MITIGATION</div>
<div style="padding:10px;background:rgba(16,185,129,0.05);border:1px dashed rgba(16,185,129,0.2);border-radius:6px;font-size:11px;color:#cbd5e1;line-height:1.6">
<strong style="color:#6ee7b7">Couverture: ${risks.length} risques cartographies, 25 agents mitigation actifs</strong><br>
<span style="color:#fca5a5">OPEN: ${open}</span> &bull; <span style="color:#f59e0b">MONITORED: ${mon}</span> &bull; <span style="color:#22c55e">CLOSED: ${closed}</span><br>
<strong>Plan action:</strong> <span style="color:#22c55e">✅ TARGET 20 CLOSED ACHIEVED</span> · Roadmap 100% : 4 risques long-terme (R18 R22 R24 R25) + R07 monitored · 24 agents actifs sur 25
</div>
</div>`;
}
return `<div class="pilot-kpi-card">
<h5>${g.icon} ${g.label}</h5>
<div class="pilot-kpi-list">
${items.map(k => `<div class="pilot-row ${k.status||''}"><div class="l">${k.label}</div><div class="v">${k.value}${k.unit||''}${k.target?' / '+k.target+(k.unit||''):''}</div></div>`).join('')}
</div>
${extra}
</div>`;
}).join('');
}
function renderERPGaps(){
const wrap = document.getElementById('erp-gap-grid');
const gaps = DATA.erp_gaps || {};
wrap.innerHTML = Object.entries(gaps).map(([key, d]) => `
<div class="erp-dept">
<h4>${d.icon} ${d.label}</h4>
<div class="erp-native">
${(d.sap_native||[]).map(x => `<span class="sap">SAP ${x}</span>`).join('')}
${(d.oracle_native||[]).map(x => `<span class="oracle">Oracle ${x}</span>`).join('')}
${(d.sage_native||[]).map(x => `<span class="sage">${x}</span>`).join('')}
</div>
${(d.erp_gaps||[]).map(g => `
<div class="erp-gap-row">
<div class="erp-gap-title">${g.gap}</div>
<div class="erp-gap-agent">🤖 ${g.wevalmine_agent}</div>
<div class="erp-gap-cant">ERP limite : ${g.erp_cant}</div>
<div class="erp-gap-verts">
${(g.vertical_impact||[]).map(v => `<span>${v}</span>`).join('')}
</div>
</div>
`).join('')}
</div>
`).join('');
}
function renderVerticals(){
const wrap = document.getElementById('vert-grid');
const verts = DATA.verticals || [];
wrap.innerHTML = verts.map(v => `
<div class="vert" style="--vcol: ${v.color}">
<h4>${v.label}</h4>
<div class="erp">${v.erp_dominant}</div>
<div class="stats">
<div class="stat"><div class="stat-v">${v.agents_pack_size}</div><div class="stat-l">Agents pack</div></div>
<div class="stat"><div class="stat-v">${(v.revenue_target_per_client/1000).toFixed(0)}k€</div><div class="stat-l">Rev/client</div></div>
<div class="stat"><div class="stat-v">${v.clients_pipeline.length}</div><div class="stat-l">Pipeline</div></div>
</div>
<div class="vert-gaps">
<strong>Top gaps couverts</strong>
${(v.top_gaps||[]).join(' · ')}
</div>
<div class="vert-clients">
${(v.clients_pipeline||[]).map(c => `<span>${c}</span>`).join('')}
</div>
</div>
`).join('');
}
function renderServices(){
const wrap = document.getElementById('services-grid');
const svc = DATA.services_offered || {};
const order = ['discovery', 'poc', 'rollout', 'managed'];
wrap.innerHTML = order.filter(k => svc[k]).map(k => {
const s = svc[k];
return `<div class="service">
<h5>${k}</h5>
<div class="duration">${s.duration}</div>
<div class="price">${s.price}</div>
<div class="deliverable">${s.deliverable}</div>
</div>`;
}).join('');
}
load();
</script>
<!-- V67 ERP AGENTS REGISTRY BADGE ENRICHMENT (injected by Opus 19avr) -->
<style>
.v67-badge{display:inline-block;margin-left:8px;padding:2px 8px;border-radius:10px;font-size:.72rem;font-weight:600;background:rgba(34,197,94,.15);color:#22c55e;border:1px solid rgba(34,197,94,.3);vertical-align:middle}
.v67-savings{display:inline-block;margin-left:6px;padding:2px 8px;border-radius:10px;font-size:.72rem;font-weight:600;background:rgba(251,191,36,.12);color:#fbbf24;border:1px solid rgba(251,191,36,.25);vertical-align:middle}
.v67-kpi{position:fixed;bottom:16px;right:16px;background:rgba(15,23,42,.92);border:1px solid rgba(148,163,184,.25);padding:10px 14px;border-radius:10px;font-size:.78rem;color:#e2e8f0;backdrop-filter:blur(8px);z-index:999;max-width:280px}
.v67-kpi b{color:#22c55e}
.v67-kpi .s{color:#fbbf24;font-weight:700}
</style>
<script>
(function(){
var tries=0,max=30;
function norm(s){return (s||'').toLowerCase().replace(/[^a-z0-9]/g,'');}
function enrich(){
tries++;
if(tries>max) return;
fetch('/api/wevia-v67-erp-agents-registry.php?t='+Date.now())
.then(r=>r.json())
.then(function(d){
if(!d||!d.agents)return;
var byName={};
d.agents.forEach(function(a){byName[norm(a.name)]=a;});
// Find all agent-name elements rendered by V65 (delayed by JS)
var items=document.querySelectorAll('.gap-item, .erp-gap, [class*="agent"], strong, .gap-row');
var hits=0;
items.forEach(function(el){
var t=(el.textContent||'').trim();
var k=norm(t);
// Try direct match or contains
var match=byName[k];
if(!match){
for(var key in byName){
if(k.length>8 && (k.indexOf(key)>=0 || key.indexOf(k)>=0)){match=byName[key];break;}
}
}
if(match && !el.querySelector('.v67-badge')){
var b=document.createElement('span');
b.className='v67-badge';
b.textContent='✅ Registered';
b.title='Agent V67 registry • dept: '+match.department;
var s=document.createElement('span');
s.className='v67-savings';
s.textContent='💰 '+Math.round((match.savings_eur_year||0)/1000)+'k€/an';
el.appendChild(b);el.appendChild(s);
hits++;
}
});
if(hits===0 && tries<max){setTimeout(enrich,800);return;}
// Global KPI card
if(!document.getElementById('v67-kpi')){
var k=document.createElement('div');
k.id='v67-kpi';k.className='v67-kpi';
k.innerHTML='<div><b>V67 Registry</b> — '+d.erp_agents+' ERP agents / '+d.total_agents+' total</div>'+
'<div>Savings potentiel : <span class="s">'+(d.savings_eur_year_total/1000000).toFixed(2)+' M€/an</span></div>'+
'<div style="opacity:.7;font-size:.7rem;margin-top:4px">Paperclip source-of-truth • API V67</div>';
document.body.appendChild(k);
}
}).catch(function(e){if(tries<max)setTimeout(enrich,1000);});
}
// Start after initial V65 render
if(document.readyState==='complete') setTimeout(enrich,1500);
else window.addEventListener('load',function(){setTimeout(enrich,1500);});
})();
</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 t32b4) --><script src="/wtp-unified-dock.js" defer></script>
</body>
</html>