723 lines
34 KiB
HTML
723 lines
34 KiB
HTML
<!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} • ${r.title}</div>
|
||
<div style="margin-top:3px;color:#94a3b8;font-size:10.5px">${r.dept} • ${r.erp} • Likelihood ${r.likelihood}/5 • Impact ${r.impact}/5 • <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> • <span style="color:#f59e0b">MONITORED: ${mon}</span> • <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 === -->
|
||
|
||
</body>
|
||
</html>
|