Files
html/pain-points-atlas.html

628 lines
37 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 · Pain Points Atlas — Tous ERPs × Agents Autonomes × Savings €</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; --amber:#f59e0b;
--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 65%);
color: var(--text); min-height: 100vh; font-size: 13.5px; line-height: 1.55;
}
.container { max-width: 1680px; margin: 0 auto; padding: 30px 36px 80px; }
/* HEADER */
header {
display: flex; justify-content: space-between; align-items: flex-start;
margin-bottom: 26px; padding-bottom: 20px; border-bottom: 1px solid var(--border);
}
header h1 {
font-size: 28px; font-weight: 800;
background: linear-gradient(90deg, #22d3ee, #a855f7, #eab308);
-webkit-background-clip: text; background-clip: text; color: transparent;
letter-spacing: -0.5px;
}
header .sub { color: var(--dim); font-size: 13.5px; margin-top: 7px; max-width: 850px; }
header .pitch {
margin-top: 12px; padding: 11px 16px;
background: linear-gradient(135deg, rgba(234,179,8,0.08), rgba(168,85,247,0.06));
border-left: 3px solid var(--gold);
border-radius: 6px; font-size: 13px; color: var(--text); line-height: 1.55;
}
.actions { display: flex; gap: 9px; }
.btn { padding: 8px 15px; background: var(--bg-2); border: 1px solid var(--border); color: var(--text); border-radius: 8px; font-size: 12.5px; cursor: pointer; text-decoration: none; font-family: inherit; transition: all .2s; display: inline-flex; gap: 6px; align-items: center; }
.btn:hover { border-color: var(--accent); color: var(--accent); }
.btn-pri { background: linear-gradient(135deg, var(--gold), var(--amber)); border: none; color: #0b0d15; font-weight: 700; }
.pulse { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 0 0 rgba(34,197,94,.7); animation: pulse 2s infinite; margin-right: 4px; }
@keyframes pulse { 0%{box-shadow:0 0 0 0 rgba(34,197,94,.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 STRIP */
.kpi-strip { display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px; margin-bottom: 28px; }
.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(--cyan); }
.kpi.c4::before { background: var(--rose); }
.kpi.gold::before { background: linear-gradient(180deg, var(--gold), var(--amber)); }
.kpi .lbl { color: var(--dim); font-size: 10.5px; text-transform: uppercase; letter-spacing: .7px; margin-bottom: 6px; font-weight: 600; }
.kpi .val { font-size: 28px; font-weight: 800; color: var(--text); line-height: 1; letter-spacing: -0.3px; }
.kpi .val.gold { background: linear-gradient(135deg, var(--gold), var(--amber)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.kpi .u { font-size: 13px; color: var(--dim); font-weight: 500; margin-left: 3px; }
.kpi .sub { color: var(--dim); font-size: 10.5px; margin-top: 5px; }
/* SECTION TITLE */
.st { font-size: 17px; font-weight: 700; color: var(--text); margin: 36px 0 14px; display: flex; align-items: center; gap: 10px; letter-spacing: -0.2px; }
.st::before { content: ''; width: 4px; height: 20px; background: linear-gradient(180deg, var(--accent), var(--purple)); border-radius: 2px; }
.st .suf { color: var(--dim); font-size: 12.5px; font-weight: 500; }
/* ERP GRID */
.erp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 10px; }
.erp-card { background: var(--bg-1); border: 1px solid var(--border); border-radius: 10px; padding: 12px; cursor: default; transition: all .2s; position: relative; }
.erp-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.erp-card::before { content: ''; position: absolute; left: 0; top: 0; width: 3px; height: 100%; border-radius: 10px 0 0 10px; background: var(--tier-color, var(--accent)); }
.erp-card .tier { font-size: 9px; padding: 1px 6px; border-radius: 8px; text-transform: uppercase; font-weight: 700; letter-spacing: 0.3px; display: inline-block; margin-bottom: 5px; }
.erp-card .tier.enterprise { background: rgba(239,68,68,0.15); color: #fca5a5; }
.erp-card .tier.mid-market { background: rgba(245,158,11,0.15); color: #fbbf24; }
.erp-card .tier.smb { background: rgba(99,102,241,0.15); color: #a5b4fc; }
.erp-card .tier.niche { background: rgba(168,85,247,0.15); color: #d4a7fa; }
.erp-card .tier.open-source { background: rgba(34,197,94,0.15); color: #86efac; }
.erp-card .name { font-size: 12px; font-weight: 600; color: var(--text); }
.erp-card .count { font-size: 10.5px; color: var(--accent); margin-top: 4px; font-family: 'JetBrains Mono',monospace; }
/* FILTER BAR */
.filter-bar { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; }
.filter-pill { padding: 7px 12px; background: var(--bg-2); border: 1px solid var(--border); color: var(--dim); border-radius: 18px; font-size: 11.5px; cursor: pointer; font-family: inherit; transition: all .2s; }
.filter-pill:hover { color: var(--text); border-color: var(--accent); }
.filter-pill.active { background: linear-gradient(135deg, var(--accent2), var(--purple)); color: white; border: none; font-weight: 600; }
/* PAIN POINT CARDS */
.pp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 14px; }
.pp-card { background: var(--bg-1); border: 1px solid var(--border); border-radius: 12px; padding: 18px; position: relative; overflow: hidden; transition: all .2s; }
.pp-card:hover { border-color: var(--border-h); }
.pp-card::before { content: ''; position: absolute; left: 0; top: 0; width: 4px; height: 100%; background: linear-gradient(180deg, var(--err), var(--amber)); }
.pp-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; margin-bottom: 10px; }
.pp-id { font-family: 'JetBrains Mono',monospace; color: var(--mute); font-size: 10.5px; font-weight: 600; }
.pp-dept-tag { font-size: 9.5px; padding: 2px 7px; border-radius: 8px; background: rgba(99,102,241,0.15); color: #a5b4fc; font-weight: 600; letter-spacing: 0.3px; text-transform: uppercase; }
.pp-pain { font-size: 13.5px; color: var(--text); font-weight: 600; line-height: 1.35; margin-bottom: 7px; display: flex; gap: 6px; }
.pp-pain::before { content: '🔴'; flex-shrink: 0; margin-top: 1px; }
.pp-biz { font-size: 11.5px; color: var(--dim); margin-bottom: 12px; line-height: 1.45; padding-left: 20px; }
.pp-why { font-size: 10.5px; color: var(--mute); margin-bottom: 10px; font-style: italic; padding: 8px 10px; background: var(--bg-2); border-radius: 6px; }
.pp-why strong { color: var(--err); display: block; margin-bottom: 2px; font-style: normal; font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; }
.pp-arrow { text-align: center; color: var(--accent); font-size: 18px; margin: 4px 0 6px; }
.pp-agent-box { padding: 10px 12px; background: linear-gradient(135deg, rgba(20,184,166,0.08), rgba(99,102,241,0.05)); border: 1px solid rgba(20,184,166,0.25); border-radius: 8px; margin-bottom: 10px; }
.pp-agent-label { font-size: 9.5px; color: var(--accent); font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 3px; }
.pp-agent-name { font-size: 12.5px; color: var(--text); font-weight: 700; display: flex; gap: 5px; align-items: center; }
.pp-agent-name::before { content: '🤖'; }
.pp-agent-how { font-size: 10.5px; color: var(--dim); margin-top: 5px; line-height: 1.45; }
.pp-savings { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: linear-gradient(135deg, rgba(234,179,8,0.08), rgba(168,85,247,0.04)); border: 1px solid rgba(234,179,8,0.25); border-radius: 8px; }
.pp-sav-lbl { font-size: 10px; color: var(--gold); font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; }
.pp-sav-lbl::before { content: '💰 '; }
.pp-sav-val { font-size: 16px; font-weight: 800; background: linear-gradient(135deg, var(--gold), var(--amber)); -webkit-background-clip: text; background-clip: text; color: transparent; font-family: 'JetBrains Mono',monospace; }
.pp-sav-note { font-size: 9.5px; color: var(--dim); margin-top: 3px; grid-column: 2; text-align: right; }
.pp-erps { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border); }
.pp-erps span { font-size: 9px; padding: 2px 6px; background: var(--bg-2); border-radius: 4px; color: var(--mute); font-family: 'JetBrains Mono',monospace; }
/* FRAMEWORK SHOWCASE */
.framework-card { background: linear-gradient(135deg, rgba(20,184,166,0.06), rgba(168,85,247,0.06)); border: 1px solid var(--border); border-radius: 14px; padding: 24px; margin-bottom: 18px; }
.framework-card h3 { font-size: 18px; color: var(--text); margin-bottom: 6px; }
.framework-card .desc { color: var(--dim); font-size: 13px; margin-bottom: 20px; }
.framework-steps { display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; }
@media(max-width: 1200px) { .framework-steps { grid-template-columns: repeat(3, 1fr); } }
@media(max-width: 640px) { .framework-steps { grid-template-columns: repeat(2, 1fr); } }
.fw-step { background: var(--bg-1); padding: 14px; border-radius: 10px; text-align: center; border: 1px solid var(--border); position: relative; }
.fw-step-num { position: absolute; top: -10px; left: 10px; width: 24px; height: 24px; border-radius: 50%; background: linear-gradient(135deg, var(--accent), var(--purple)); display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 800; color: white; }
.fw-step-text { font-size: 11.5px; color: var(--text); line-height: 1.4; margin-top: 6px; }
/* DOGFOOD */
.dogfood { background: linear-gradient(135deg, rgba(234,179,8,0.10), rgba(168,85,247,0.06)); border: 1px solid rgba(234,179,8,0.25); border-radius: 14px; padding: 22px; display: grid; grid-template-columns: 80px 1fr auto; gap: 20px; align-items: center; }
.dogfood .icon { font-size: 50px; }
.dogfood h3 { font-size: 16px; color: var(--text); margin-bottom: 5px; }
.dogfood p { color: var(--dim); font-size: 12.5px; line-height: 1.55; }
.dogfood .stat { text-align: right; }
.dogfood .stat-v { font-size: 26px; font-weight: 800; color: var(--gold); font-family: 'JetBrains Mono',monospace; }
.dogfood .stat-l { font-size: 10.5px; color: var(--dim); text-transform: uppercase; letter-spacing: 0.5px; }
@media(max-width: 1024px) {
.kpi-strip { grid-template-columns: repeat(2, 1fr); }
header { flex-direction: column; gap: 14px; }
.dogfood { grid-template-columns: 1fr; text-align: center; }
.dogfood .stat { text-align: center; }
}
.loading { text-align: center; padding: 60px; color: var(--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); } }
/* === OPUS RESPONSIVE FIX v2 19avr — append-only, doctrine #14 === */
@media(max-width: 480px) {
html, body { overflow-x: hidden; max-width: 100vw; }
.container { padding: 16px 12px 40px; max-width: 100vw; }
header, header > * { width: 100%; max-width: 100%; }
header h1 { font-size: 22px; word-break: break-word; }
header .sub { font-size: 12.5px; word-break: break-word; max-width: 100%; }
.pitch, header .pitch { word-break: break-word; overflow-wrap: anywhere; font-size: 12px; }
.kpi-strip { grid-template-columns: 1fr; gap: 10px; }
.erp-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; }
.pp-grid { grid-template-columns: 1fr; gap: 12px; }
.filter-bar { gap: 4px; flex-wrap: wrap; }
.dogfood { padding: 16px; grid-template-columns: 1fr; }
.dogfood .icon { font-size: 36px; }
.dogfood .stat-v { font-size: 20px; }
.actions { flex-wrap: wrap; width: 100%; }
.actions .btn { font-size: 11.5px; padding: 6px 10px; }
.pp-card, .erp-card { max-width: 100%; word-break: break-word; }
pre, code { word-break: break-all; white-space: pre-wrap; }
}
@media(max-width: 360px) {
.erp-grid { grid-template-columns: 1fr; }
}
/* === OPUS RESPONSIVE FIX v2 END === */
</style>
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-143847 -->
<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>🗺️ Pain Points Atlas <span class="pulse"></span></h1>
<div class="sub">Tous les ERPs du marché · Pain points business catalogués · Agents autonomes WEVAL · Savings €/an quantifiés</div>
<div class="pitch" id="pitch-t">Chargement du pitch…</div>
</div>
<div class="actions">
<a href="/erp-gap-fill-offer.html" class="btn">← Offre V65</a>
<a href="/weval-technology-platform.html" class="btn">🏠 WTP</a>
<button class="btn btn-pri" onclick="window.print()">📑 Export</button>
</div>
</header>
<!-- KPI STRIP -->
<div class="kpi-strip" id="kpi-strip">
<div class="kpi"><div class="lbl">ERPs couverts</div><div class="val" id="k-erps"></div><div class="sub">S/4HANA · Oracle · Sage · Odoo · D365 · NetSuite · Workday · SF · Infor · IFS · Epicor…</div></div>
<div class="kpi c2"><div class="lbl">Pain points catalogués</div><div class="val" id="k-pp"></div><div class="sub">9 départements métier</div></div>
<div class="kpi c3"><div class="lbl">Agents WEVAL uniques</div><div class="val" id="k-ag"></div><div class="sub">1 pain = 1 agent autonome</div></div>
<div class="kpi c4"><div class="lbl">Savings moyen/agent</div><div class="val" id="k-avg"><span class="u">€/an</span></div><div class="sub">ROI mesurable</div></div>
<div class="kpi gold"><div class="lbl">Savings total/client</div><div class="val gold" id="k-tot"><span class="u">M€/an</span></div><div class="sub" id="k-tot-sub">potentiel maximal pack complet</div></div>
</div>
<!-- ERPs COVERED -->
<div class="st">📦 25 ERPs du marché couverts <span class="suf">— chaque ERP a ses gaps, nos agents les comblent</span></div>
<div class="erp-grid" id="erp-grid"><div class="loading"><div class="spinner"></div></div></div>
<!-- FILTER BAR -->
<div class="st">🔴 Pain Points → 🤖 Agents → 💰 Savings <span class="suf">— filtrer par département</span></div>
<div class="filter-bar" id="filter-bar"></div>
<!-- PAIN POINTS -->
<div class="pp-grid" id="pp-grid"><div class="loading"><div class="spinner"></div></div></div>
<!-- FRAMEWORK -->
<div class="st">🎯 Framework WEVAL <span class="suf">— méthodologie éprouvée, adaptable par client</span></div>
<div class="framework-card">
<h3 id="fw-title">WEVAL Gap-Fill Framework</h3>
<div class="desc" id="fw-desc">Pain Point Business → Agent Autonome → Savings €/an</div>
<div class="framework-steps" id="fw-steps"></div>
</div>
<!-- DOGFOOD -->
<div class="st">🐕 Preuve par l'exemple <span class="suf">— WEVAL applique son propre framework (dogfooding)</span></div>
<div class="dogfood">
<div class="icon">🎯</div>
<div>
<h3>WEVAL utilise son propre framework</h3>
<p id="dog-pitch">Chargement…</p>
<p style="margin-top:8px"><a href="/weval-technology-platform.html" style="color:var(--accent);text-decoration:none;font-weight:600">→ Voir WEVAL Technology Platform (WTP)</a> qui applique ces agents sur nos propres pain points.</p>
</div>
<div class="stat">
<div class="stat-v" id="dog-sav"></div>
<div class="stat-l">Savings WEVAL interne/an</div>
<div class="stat-v" id="dog-gaps" style="margin-top:10px"></div>
<div class="stat-l">Gaps WEVAL comblés</div>
</div>
</div>
<!-- V96 19avr: SCAN PANEL — Gaps détectés depuis sources web (LLM+RSS+Playwright) -->
<section style="margin-top:42px;padding:24px 26px;background:linear-gradient(135deg,rgba(168,85,247,0.08),rgba(6,182,212,0.06));border:1px solid rgba(168,85,247,0.3);border-radius:14px">
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:16px;flex-wrap:wrap;gap:12px">
<div>
<h2 style="font-size:22px;font-weight:800;background:linear-gradient(90deg,#a855f7,#06b6d4);-webkit-background-clip:text;background-clip:text;color:transparent;margin-bottom:4px">🛰️ Scan Panel · Gaps détectés automatiquement</h2>
<p style="color:var(--dim);font-size:13px;max-width:780px">Pipeline 3-sources: <strong style="color:#a855f7">LLM</strong> (Cerebras sovereign 0€) · <strong style="color:#14b8a6">RSS</strong> (CIO, ERPToday, Reddit) · <strong style="color:#06b6d4">Playwright</strong> (TrustRadius reviews). Cron hebdo dimanche 3h. <span class="pulse"></span> données live · <a href="/api/erp-gap-scans.php" style="color:var(--accent)" target="_blank">JSON API</a></p>
</div>
<button id="scan-refresh-btn" style="padding:8px 14px;background:linear-gradient(135deg,#a855f7,#06b6d4);border:none;color:#0b0d15;border-radius:8px;font-weight:700;cursor:pointer;font-size:12px">🔄 Refresh</button>
</div>
<div id="scan-stats" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;margin-bottom:16px"></div>
<div id="scan-per-erp" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:10px;margin-bottom:20px"></div>
<details style="margin-top:8px">
<summary style="cursor:pointer;color:var(--dim);font-size:13px;font-weight:600;user-select:none">📋 Voir les 20 gaps les plus confiants (détails)</summary>
<div id="scan-details" style="margin-top:12px;display:grid;gap:8px"></div>
</details>
</section>
</div>
<script>
// V96: Load scan data from /api/erp-gap-scans.php
async function loadScanPanel() {
try {
const r = await fetch('/api/erp-gap-scans.php?limit=20&min_conf=0.5&t=' + Date.now());
const d = await r.json();
if (!d.stats) return;
const s = d.stats;
// Stats tiles
document.getElementById('scan-stats').innerHTML = [
['Total gaps', s.total_gaps, '#a855f7'],
['ERPs couverts', s.erps_covered + '/25', '#06b6d4'],
['Confiance moy.', s.avg_confidence, '#14b8a6'],
['Dernière scan', (s.last_scan_at||'?').substring(0,16).replace('T',' '), '#eab308'],
['LLM (D)', s.gaps_llm, '#a855f7'],
['RSS (C)', s.gaps_rss, '#14b8a6'],
['Playwright (B)', s.gaps_playwright, '#06b6d4']
].map(([lbl,val,color]) => `<div style="background:var(--bg-2);border:1px solid var(--border);border-radius:10px;padding:12px 14px;position:relative;overflow:hidden"><div style="position:absolute;left:0;top:0;width:3px;height:100%;background:${color}"></div><div style="color:var(--dim);font-size:10px;text-transform:uppercase;letter-spacing:.5px;font-weight:600;margin-bottom:4px">${lbl}</div><div style="font-size:20px;font-weight:800;color:var(--text)">${val}</div></div>`).join('');
// Per-ERP grid
document.getElementById('scan-per-erp').innerHTML = d.per_erp.map(e => {
const _rawSrc=e.sources;const _srcArr=Array.isArray(_rawSrc)?_rawSrc:(typeof _rawSrc==='string'?_rawSrc.split(/[,;|]/).map(function(s){return s.trim();}):(_rawSrc&&typeof _rawSrc==='object'?Object.values(_rawSrc).map(function(v){return String(v);}):[]));const srcs=_srcArr.filter(function(x){return x&&x!=='Other';}).join(' · ')||'Other';
const conf = parseFloat(e.avg_conf)||0;
const confColor = conf >= 0.7 ? '#22c55e' : (conf >= 0.5 ? '#eab308' : '#f43f5e');
return `<div style="background:var(--bg-2);border:1px solid var(--border);border-radius:8px;padding:10px 12px;display:flex;justify-content:space-between;align-items:center;gap:8px" onmouseover="this.style.borderColor='var(--accent)'" onmouseout="this.style.borderColor='var(--border)'">
<div style="flex:1;min-width:0;overflow:hidden">
<div style="font-weight:700;font-size:12.5px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${e.erp_name || e.erp_id}</div>
<div style="font-size:10px;color:var(--dim);margin-top:2px">${srcs}</div>
</div>
<div style="display:flex;align-items:center;gap:8px;flex-shrink:0">
<div style="color:${confColor};font-size:11px;font-weight:700">${conf}</div>
<div style="background:${confColor};color:#0b0d15;border-radius:100px;padding:2px 8px;font-size:11px;font-weight:800">${e.gaps_count}</div>
</div>
</div>`;
}).join('');
// Details
document.getElementById('scan-details').innerHTML = (d.gaps||[]).slice(0,20).map(g => {
const srcColor = g.source==='LLM'?'#a855f7' : g.source==='RSS'?'#14b8a6' : g.source==='Playwright'?'#06b6d4' : '#64748b';
const linkPart = (g.source_url && !g.source_url.startsWith('llm://')) ? `<a href="${g.source_url}" target="_blank" rel="noopener" style="color:var(--accent);font-size:10px;margin-left:8px">↗ source</a>` : '';
return `<div style="background:var(--bg-1);border-left:3px solid ${srcColor};border-radius:4px;padding:9px 14px;font-size:12px">
<div style="display:flex;justify-content:space-between;gap:10px;margin-bottom:3px;flex-wrap:wrap">
<div><span style="font-weight:700;color:var(--text)">${g.erp_name || g.erp_id}</span> <span style="color:${srcColor};font-size:10px;font-weight:600;margin-left:4px">${g.source}</span> <span style="color:var(--mute);font-size:10px">· conf=${g.confidence_score}</span>${linkPart}</div>
</div>
<div style="color:var(--dim);font-size:11.5px;line-height:1.5">${(g.snippet||g.title||'').substring(0,280)}${(g.snippet||'').length>280?'…':''}</div>
</div>`;
}).join('') || '<div style="color:var(--dim);font-size:12px">Aucun gap (min_conf=0.5). Baissez le seuil via API: <code>?min_conf=0.3</code></div>';
} catch(e) { console.error('scan-panel:', e); }
}
document.getElementById('scan-refresh-btn').onclick = loadScanPanel;
loadScanPanel();
setInterval(loadScanPanel, 90000);
</script>
<script>
const API = '/api/wevia-v66-all-erps-painpoints.php';
let DATA = null;
let currentFilter = 'all';
async function load(){
try {
const r = await fetch(API + '?t=' + Date.now());
DATA = await r.json(); window.DATA = DATA;
render();
} catch(e) { console.error(e); }
}
function fmtEur(n){
if (n >= 1000000) return (n/1000000).toFixed(2) + 'M€';
if (n >= 1000) return (n/1000).toFixed(0) + 'k€';
return n + '€';
}
function render(){
if (!DATA) return;
const s = DATA.summary;
if (DATA.pitch) document.getElementById('pitch-t').textContent = '💡 ' + DATA.pitch;
document.getElementById('k-erps').textContent = s.erp_vendors_covered;
document.getElementById('k-pp').textContent = s.pain_points_total;
document.getElementById('k-ag').textContent = s.agents_unique;
document.getElementById('k-avg').innerHTML = Math.round(s.avg_savings_per_agent_eur/1000) + '<span class="u">k€/an</span>';
document.getElementById('k-tot').innerHTML = s.savings_per_client_year_m_eur + '<span class="u">M€/an</span>';
document.getElementById('k-tot-sub').textContent = s.savings_per_client_year_eur.toLocaleString('fr-FR') + ' €/an (tous agents déployés)';
// ERPs grid
const erps = DATA.erp_vendors || {};
const mentions = DATA.erp_mentions_top || {};
const erpWrap = document.getElementById('erp-grid');
erpWrap.innerHTML = Object.entries(erps).map(([k, v]) => {
const count = mentions[k] || 0;
return `<div class="erp-card">
<div class="tier ${v.tier.replace('_','-')}">${v.tier}</div>
<div class="name">${v.label}</div>
<div class="count">🚧 ${count} gap${count>1?'s':''}</div>
</div>`;
}).join('');
// Filter bar
const depts = [...new Set((DATA.pain_points||[]).map(p => p.dept))];
const deptLabels = {
finance:'💰 Finance',
supply:'📦 Supply',
manufacturing:'🏭 Manufacturing',
sales:'💼 Sales/CRM',
hr:'👥 HR',
marketing:'📈 Marketing',
security:'🔐 Security',
operations:'⚙️ IT Ops',
direction:'👔 Direction'
};
const fBar = document.getElementById('filter-bar');
fBar.innerHTML = `<button class="filter-pill ${currentFilter==='all'?'active':''}" data-f="all">🌐 Tous (${DATA.pain_points.length})</button>` +
depts.map(d => {
const count = DATA.pain_points.filter(p => p.dept === d).length;
return `<button class="filter-pill ${currentFilter===d?'active':''}" data-f="${d}">${deptLabels[d]||d} (${count})</button>`;
}).join('');
fBar.querySelectorAll('.filter-pill').forEach(b => b.onclick = () => {
currentFilter = b.dataset.f;
renderPP();
fBar.querySelectorAll('.filter-pill').forEach(x => x.classList.toggle('active', x.dataset.f === currentFilter));
});
renderPP();
// Framework
const fw = DATA.arch_injection && DATA.arch_injection.framework;
if (fw) {
document.getElementById('fw-desc').textContent = fw.pitch;
document.getElementById('fw-steps').innerHTML = (fw.steps||[]).map((s,i) => `<div class="fw-step"><div class="fw-step-num">${i+1}</div><div class="fw-step-text">${s.replace(/^\d+\.\s*/,'')}</div></div>`).join('');
}
// Dogfood
const dog = DATA.dogfood_weval;
if (dog) {
document.getElementById('dog-pitch').textContent = dog.pitch;
document.getElementById('dog-sav').textContent = fmtEur(dog.weval_self_savings_eur_year);
document.getElementById('dog-gaps').textContent = dog.weval_self_gaps_fixed;
}
}
function renderPP(){
const wrap = document.getElementById('pp-grid');
let pps = DATA.pain_points || [];
if (currentFilter !== 'all') pps = pps.filter(p => p.dept === currentFilter);
pps = pps.sort((a,b) => b.savings_eur_year - a.savings_eur_year);
wrap.innerHTML = pps.map(p => `
<div class="pp-card">
<div class="pp-head">
<div class="pp-id">${p.id}</div>
<div class="pp-dept-tag">${p.dept}</div>
</div>
<div class="pp-pain">${p.pain}</div>
<div class="pp-biz">${p.business_cost}</div>
<div class="pp-why"><strong>Pourquoi l'ERP échoue</strong>${p.why_erp_fails}</div>
<div class="pp-arrow">↓</div>
<div class="pp-agent-box">
<div class="pp-agent-label">Agent WEVAL</div>
<div class="pp-agent-name">${p.agent}</div>
<div class="pp-agent-how">${p.agent_how}</div>
</div>
<div class="pp-savings">
<div>
<div class="pp-sav-lbl">Savings estimés</div>
<div style="font-size:9.5px;color:var(--dim);margin-top:3px">${p.savings_note}</div>
</div>
<div class="pp-sav-val">${fmtEur(p.savings_eur_year)}/an</div>
</div>
<div class="pp-erps">
${(p.erps_affected||[]).slice(0,6).map(e => `<span>${(DATA.erp_vendors[e]||{}).label || e}</span>`).join('')}
${(p.erps_affected||[]).length > 6 ? `<span>+${(p.erps_affected||[]).length-6}</span>` : ''}
</div>
</div>
`).join('');
}
load();
// === OPUS DRILL-DOWN PILOT v1 19avr — append-only, doctrine #14 ===
(function(){
if (window.__opusDrillInit) return; window.__opusDrillInit = true;
var modal = document.createElement('div');
modal.id = 'opus-drill-modal';
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.85);backdrop-filter:blur(8px);display:none;align-items:center;justify-content:center;z-index:9999;padding:24px;cursor:pointer';
var inner = document.createElement('div');
inner.id = 'opus-drill-inner';
inner.style.cssText = 'max-width:820px;width:100%;max-height:90vh;overflow:auto;background:var(--bg-1,#0b0d15);border:1px solid var(--border-h,rgba(99,102,241,0.35));border-radius:16px;padding:32px;cursor:default;box-shadow:0 20px 60px rgba(0,0,0,0.6)';
inner.addEventListener('click', function(e){ e.stopPropagation(); });
modal.appendChild(inner);
modal.addEventListener('click', function(){ modal.style.display='none'; });
document.addEventListener('keydown', function(e){ if(e.key==='Escape') modal.style.display='none'; });
if (document.body) document.body.appendChild(modal);
else document.addEventListener('DOMContentLoaded', function(){ document.body.appendChild(modal); });
function fmtEur(n){ if(!n) return '0€'; if(n>=1e6) return (n/1e6).toFixed(2)+'M€'; if(n>=1e3) return Math.round(n/1000)+'k€'; return n+'€'; }
function renderPP(pp, DATA) {
var vendors = (DATA && DATA.erp_vendors) || {};
var erps = (pp.erps_affected || []).map(function(e){
var v = vendors[e] || {};
return '<span style="display:inline-block;padding:4px 10px;margin:3px;background:rgba(99,102,241,0.08);border:1px solid rgba(99,102,241,0.25);border-radius:6px;font-size:11px;color:#e2e8f0">'+(v.label || e)+'</span>';
}).join('');
return [
'<div style="display:flex;gap:12px;margin-bottom:20px;align-items:center;flex-wrap:wrap">',
'<span style="font-family:JetBrains Mono,monospace;color:#eab308;font-weight:700;font-size:16px">'+pp.id+'</span>',
'<span style="padding:4px 12px;background:rgba(168,85,247,0.15);color:#d4a7fa;border-radius:20px;font-size:10.5px;text-transform:uppercase;letter-spacing:1px;font-weight:700">'+pp.dept+'</span>',
'<button onclick="document.getElementById(\'opus-drill-modal\').style.display=\'none\'" style="margin-left:auto;padding:6px 14px;background:#171b2a;border:1px solid rgba(99,102,241,0.15);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:12px">✕ Fermer (Esc)</button>',
'</div>',
'<h2 style="font-size:22px;margin-bottom:8px;color:#e2e8f0;line-height:1.35">'+pp.pain+'</h2>',
'<p style="color:#94a3b8;margin-bottom:24px;font-size:13.5px;line-height:1.55">'+pp.business_cost+'</p>',
'<div style="margin-bottom:20px"><div style="color:#ef4444;font-size:11px;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;font-weight:700">Pourquoi l\'ERP échoue</div><p style="font-size:13px;line-height:1.55;color:#cbd5e1">'+pp.why_erp_fails+'</p></div>',
'<div style="margin-bottom:20px;padding:18px;background:rgba(20,184,166,0.06);border:1px solid rgba(20,184,166,0.25);border-radius:12px">',
'<div style="color:#14b8a6;font-size:11px;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;font-weight:700">🤖 Agent WEVAL</div>',
'<h4 style="font-size:16px;margin-bottom:6px;color:#e2e8f0">'+pp.agent+'</h4>',
'<p style="color:#94a3b8;font-size:12.5px;line-height:1.55">'+pp.agent_how+'</p>',
'</div>',
'<div style="padding:18px;background:linear-gradient(135deg,rgba(234,179,8,0.12),rgba(168,85,247,0.08));border:1px solid rgba(234,179,8,0.3);border-radius:12px;margin-bottom:20px;display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap">',
'<div><div style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:#94a3b8;margin-bottom:4px;font-weight:700">Savings estimés</div><div style="font-size:13px;color:#94a3b8">'+(pp.savings_note || '')+'</div></div>',
'<div style="font-size:30px;font-weight:800;color:#eab308;font-family:JetBrains Mono,monospace">'+fmtEur(pp.savings_eur_year)+'<span style="font-size:14px;color:#94a3b8">/an</span></div>',
'</div>',
'<div><div style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:#94a3b8;margin-bottom:10px;font-weight:700">ERPs affectés ('+(pp.erps_affected||[]).length+')</div><div>'+erps+'</div></div>'
].join('');
}
document.addEventListener('click', function(e) {
var card = e.target.closest && e.target.closest('.pp-card');
if (!card || !card.dataset || !card.dataset.ppId) return;
var DATA = window.DATA || null;
if (!DATA) return;
var pp = (DATA.pain_points || []).find(function(p){ return p.id === card.dataset.ppId; });
if (!pp) return;
document.getElementById('opus-drill-inner').innerHTML = renderPP(pp, DATA);
modal.style.display = 'flex';
});
var observer = new MutationObserver(function(){
var cards = document.querySelectorAll('.pp-card:not([data-pp-id])');
cards.forEach(function(card){
var idEl = card.querySelector('.pp-id');
if (idEl) {
card.dataset.ppId = idEl.textContent.trim();
card.style.cursor = 'pointer';
card.setAttribute('role', 'button');
card.setAttribute('tabindex', '0');
card.setAttribute('aria-label', 'Cliquer pour voir détails pain point');
}
});
});
observer.observe(document.body || document.documentElement, {childList:true, subtree:true});
})();
// === OPUS DRILL-DOWN PILOT END ===
</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/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>