Files
html/erp-launchpad.html
2026-04-19 22:10:02 +02:00

620 lines
32 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>WEVAL · ERP Launchpad — Next-gen UX (beyond S/4HANA Fiori)</title>
<style>
:root{
--bg-0:#05060a; --bg-1:#0b0d15; --bg-2:#11141f; --bg-3:#171b2a; --bg-tile:#0f1420; --bg-hover:#1a1f30;
--border:rgba(99,102,241,0.15); --border-h:rgba(99,102,241,0.38); --border-a:rgba(20,184,166,0.5);
--text:#e2e8f0; --dim:#94a3b8; --mute:#64748b;
--accent:#14b8a6; --accent2:#6366f1; --purple:#a855f7; --cyan:#06b6d4;
--ok:#22c55e; --warn:#f59e0b; --err:#ef4444; --gold:#eab308; --rose:#f43f5e;
/* Module category colors */
--c-intel:#ec4899; --c-fin:#3b82f6; --c-commerce:#10b981; --c-mkt:#f59e0b;
--c-growth:#06b6d4; --c-hr:#a855f7; --c-supply:#06b6d4; --c-ops:#14b8a6;
--c-erp:#f43f5e; --c-comm:#6366f1; --c-sec:#ef4444; --c-dev:#8b5cf6;
--c-know:#eab308; --c-multi:#ec4899; --c-rd:#22c55e; --c-l6s:#ff6b35;
}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Inter',system-ui,sans-serif;background:radial-gradient(ellipse 1600px 900px at 30% -10%,#141a2e,#05060a 60%);color:var(--text);min-height:100vh;font-size:13px;line-height:1.5}
/* SHELL */
.shell{position:sticky;top:0;z-index:100;background:rgba(5,6,10,0.92);backdrop-filter:blur(14px);border-bottom:1px solid var(--border);padding:12px 22px;display:flex;align-items:center;gap:14px}
.brand{display:flex;align-items:center;gap:10px;font-weight:800;font-size:15px;background:linear-gradient(90deg,#22d3ee,#a855f7,#eab308);-webkit-background-clip:text;background-clip:text;color:transparent}
.brand::before{content:'';display:inline-block;width:10px;height:10px;border-radius:50%;background:var(--ok);box-shadow:0 0 0 0 rgba(34,197,94,.7);animation:pulse 2s infinite}
@keyframes pulse{0%,100%{box-shadow:0 0 0 0 rgba(34,197,94,.7)}70%{box-shadow:0 0 0 10px rgba(34,197,94,0)}}
.searchbox{flex:1;max-width:620px;position:relative}
.searchbox input{width:100%;background:var(--bg-2);border:1px solid var(--border);color:var(--text);padding:10px 16px 10px 40px;border-radius:10px;font-size:13.5px;font-family:inherit;transition:all .15s}
.searchbox input:focus{outline:none;border-color:var(--border-a);box-shadow:0 0 0 3px rgba(20,184,166,0.12)}
.searchbox::before{content:'⌕';position:absolute;left:14px;top:50%;transform:translateY(-50%);color:var(--dim);font-size:17px}
.searchbox::after{content:'⌘ K';position:absolute;right:12px;top:50%;transform:translateY(-50%);background:var(--bg-3);color:var(--mute);font-size:10px;padding:2px 7px;border-radius:4px;font-family:'JetBrains Mono',monospace}
.shell-right{display:flex;gap:6px;align-items:center}
.view-tabs{display:flex;gap:3px;background:var(--bg-2);padding:3px;border-radius:8px;border:1px solid var(--border)}
.vt{padding:5px 10px;background:transparent;border:none;color:var(--dim);font-size:11px;cursor:pointer;font-family:inherit;border-radius:5px;transition:all .15s}
.vt:hover{color:var(--text)}
.vt.active{background:var(--bg-3);color:var(--accent)}
.btn{padding:7px 12px;background:var(--bg-2);border:1px solid var(--border);color:var(--text);border-radius:8px;font-size:11.5px;cursor:pointer;text-decoration:none;font-family:inherit;transition:all .15s}
.btn:hover{border-color:var(--accent);color:var(--accent)}
/* CONTAINER */
.wrap{max-width:1760px;margin:0 auto;padding:20px 22px 100px}
/* QUICK LINKS Favoris */
.quick{background:linear-gradient(135deg,rgba(234,179,8,0.06),rgba(168,85,247,0.05));border:1px solid rgba(234,179,8,0.18);border-radius:14px;padding:14px 16px;margin-bottom:20px}
.quick-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
.quick-head .t{font-size:11px;letter-spacing:1.8px;text-transform:uppercase;color:var(--gold);font-weight:800}
.quick-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:8px}
.qt{background:var(--bg-2);border:1px solid var(--border);border-radius:10px;padding:10px 12px;cursor:pointer;transition:all .15s;text-decoration:none;color:var(--text);display:block;position:relative}
.qt:hover{border-color:var(--border-a);transform:translateY(-2px);background:var(--bg-hover)}
.qt .ic{font-size:22px;margin-bottom:5px;display:block}
.qt .n{font-size:11.5px;font-weight:700;line-height:1.2}
.qt .d{font-size:10px;color:var(--dim);margin-top:2px}
.qt-add{display:flex;align-items:center;justify-content:center;background:transparent;border:1.5px dashed var(--border);color:var(--mute);cursor:pointer;font-size:20px}
.qt-add:hover{border-color:var(--accent);color:var(--accent)}
/* Category nav rail */
.cat-rail{display:flex;gap:6px;overflow-x:auto;padding:4px 0;margin-bottom:16px;scrollbar-width:none}
.cat-rail::-webkit-scrollbar{display:none}
.cat-btn{flex-shrink:0;padding:7px 14px;background:var(--bg-2);border:1px solid var(--border);color:var(--dim);border-radius:20px;font-size:11.5px;cursor:pointer;font-family:inherit;transition:all .15s;display:flex;align-items:center;gap:6px}
.cat-btn:hover{color:var(--text);border-color:var(--border-h)}
.cat-btn.active{background:linear-gradient(135deg,var(--accent),var(--cyan));color:white;border:none;font-weight:700}
.cat-btn .cnt{font-size:10px;opacity:0.7;background:rgba(255,255,255,0.1);padding:1px 5px;border-radius:3px;margin-left:3px}
/* MODULE ACCORDION */
.module-sec{margin-bottom:18px}
.module-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--bg-1);border:1px solid var(--border);border-radius:10px;cursor:pointer;transition:all .15s}
.module-head:hover{border-color:var(--border-h)}
.module-head .chev{font-size:10px;color:var(--dim);transition:transform .2s}
.module-head.open .chev{transform:rotate(90deg)}
.module-head .ic{font-size:22px}
.module-head .info{flex:1}
.module-head .name{font-size:14px;font-weight:700;letter-spacing:0.2px}
.module-head .sub{font-size:11px;color:var(--dim);margin-top:1px}
.module-head .cnt{background:var(--bg-3);color:var(--accent);padding:3px 10px;border-radius:10px;font-size:11px;font-family:'JetBrains Mono',monospace;font-weight:700}
/* SUB-MODULE GRID — S/4 killer */
.submod-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:10px;margin-top:10px;padding:4px;max-height:0;overflow:hidden;transition:max-height .3s ease}
.module-sec.open .submod-grid{max-height:4000px}
.submod{background:var(--bg-tile);border:1px solid var(--border);border-radius:12px;overflow:hidden;transition:all .18s;position:relative;cursor:pointer;display:flex;flex-direction:column}
.submod:hover{border-color:var(--border-a);transform:translateY(-3px);box-shadow:0 12px 35px rgba(0,0,0,0.4)}
.submod::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:var(--mc);opacity:0.8}
.submod-favorite{position:absolute;top:10px;right:10px;background:transparent;border:none;color:var(--mute);font-size:16px;cursor:pointer;transition:all .15s;z-index:2}
.submod-favorite:hover{color:var(--gold);transform:scale(1.15)}
.submod-favorite.on{color:var(--gold)}
.submod-body{padding:14px 14px 10px}
.submod-head{display:flex;align-items:flex-start;gap:10px;margin-bottom:8px;padding-right:26px}
.submod-ic{width:36px;height:36px;border-radius:8px;background:linear-gradient(135deg,rgba(20,184,166,0.15),rgba(99,102,241,0.12));display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0}
.submod-name{font-size:13px;font-weight:700;color:var(--text);line-height:1.25;margin-bottom:3px}
.submod-desc{font-size:10.5px;color:var(--dim);line-height:1.4;min-height:2.6em;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.submod-kpi{display:flex;gap:10px;margin-top:10px;padding:8px 0;border-top:1px dashed var(--bg-3);font-family:'JetBrains Mono',monospace}
.kpi-mini{flex:1;text-align:center}
.kpi-mini .v{font-size:15px;font-weight:800;color:var(--accent)}
.kpi-mini .l{font-size:9px;color:var(--mute);text-transform:uppercase;letter-spacing:0.3px}
.submod-pills{display:flex;flex-wrap:wrap;gap:3px;margin-top:8px}
.pill{font-size:9.5px;padding:2px 7px;border-radius:4px;font-family:'JetBrains Mono',monospace;font-weight:600;letter-spacing:0.2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px}
.pill.page{background:rgba(34,197,94,0.12);color:#86efac}
.pill.api{background:rgba(245,158,11,0.12);color:#fbbf24}
.pill.script{background:rgba(168,85,247,0.12);color:#d4a7fa}
.pill.path{background:rgba(99,102,241,0.12);color:#a5b4fc}
.submod-foot{margin-top:auto;padding:8px 14px;background:rgba(0,0,0,0.3);border-top:1px solid rgba(255,255,255,0.04);display:flex;justify-content:space-between;align-items:center}
.submod-open{font-size:10.5px;color:var(--accent);font-weight:700;text-decoration:none}
.submod-open:hover{color:var(--cyan)}
.submod-status{font-size:9px;padding:2px 7px;border-radius:4px;font-weight:700;letter-spacing:0.3px;text-transform:uppercase}
.submod-status.live{background:rgba(34,197,94,0.18);color:#86efac}
/* COMPACT list view */
.compact-list{display:none;flex-direction:column;gap:4px}
.compact-list.active{display:flex}
.compact-item{display:grid;grid-template-columns:32px 1.5fr 2fr auto auto;gap:12px;align-items:center;padding:10px 14px;background:var(--bg-1);border:1px solid var(--border);border-radius:8px;cursor:pointer;transition:all .1s}
.compact-item:hover{background:var(--bg-hover);border-color:var(--border-h)}
.compact-item .c-ic{font-size:18px}
.compact-item .c-n{font-size:12.5px;font-weight:700}
.compact-item .c-d{font-size:11px;color:var(--dim)}
.compact-item .c-m{font-size:10px;color:var(--mute);font-family:'JetBrains Mono',monospace}
.compact-item .c-a{color:var(--accent);font-size:13px}
/* Command K overlay */
.cmdk{position:fixed;inset:0;background:rgba(0,0,0,0.7);backdrop-filter:blur(8px);display:none;z-index:200;align-items:flex-start;justify-content:center;padding-top:90px}
.cmdk.show{display:flex}
.cmdk-box{background:var(--bg-1);border:1px solid var(--border-h);border-radius:14px;width:90%;max-width:640px;max-height:70vh;overflow:hidden;box-shadow:0 20px 80px rgba(0,0,0,0.5);display:flex;flex-direction:column}
.cmdk-input{width:100%;background:transparent;border:none;color:var(--text);padding:18px 22px;font-size:16px;border-bottom:1px solid var(--border);font-family:inherit}
.cmdk-input:focus{outline:none}
.cmdk-results{flex:1;overflow-y:auto;padding:8px}
.cmdk-item{padding:10px 14px;border-radius:8px;cursor:pointer;display:flex;align-items:center;gap:12px;transition:all .1s}
.cmdk-item:hover,.cmdk-item.selected{background:var(--bg-3)}
.cmdk-item .icon{font-size:20px}
.cmdk-item .txt{flex:1}
.cmdk-item .t-n{font-size:13px;font-weight:600}
.cmdk-item .t-m{font-size:11px;color:var(--dim);margin-top:1px}
.cmdk-footer{padding:10px 20px;border-top:1px solid var(--border);font-size:10.5px;color:var(--mute);display:flex;gap:14px}
/* Clock */
.clock{font-family:'JetBrains Mono',monospace;color:var(--accent);font-size:10px}
/* Empty */
.empty{text-align:center;padding:60px;color:var(--dim)}
.spinner{width:32px;height:32px;border:3px solid var(--bg-3);border-top-color:var(--accent);border-radius:50%;margin:0 auto 12px;animation:spin 1s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
/* Responsive */
@media(max-width:900px){
.searchbox{max-width:none}
.cmdk-box{width:96%;max-width:none}
.view-tabs{display:none}
}
/* === 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="shell">
<div class="brand">WEVAL · Launchpad</div>
<div class="searchbox">
<input id="search" placeholder="Rechercher page, API, agent, KPI, dept (Cmd-K pour recherche globale)" autocomplete="off">
</div>
<div class="shell-right">
<div class="view-tabs">
<button class="vt active" data-view="grid">▦ Grid</button>
<button class="vt" data-view="compact">☰ Compact</button>
</div>
<a href="/weval-technology-platform.html" class="btn">← WTP</a>
<a href="/enterprise-complete.html" class="btn">🏢 Enterprise</a>
<a href="/sales-hub.html" class="btn">🚀 Sales</a>
</div>
</div>
<div class="wrap">
<!-- QUICK / FAVORIS -->
<div class="quick">
<div class="quick-head">
<div class="t">⭐ Mes favoris · quick actions</div>
<span class="clock" id="clock"></span>
</div>
<div class="quick-tiles" id="quick-tiles"></div>
</div>
<!-- CAT RAIL -->
<div class="cat-rail" id="cat-rail"></div>
<!-- MODULES -->
<div id="grid-view">
<div class="empty"><div class="spinner"></div>Chargement modules ERP…</div>
</div>
<!-- COMPACT view -->
<div class="compact-list" id="compact-view"></div>
</div>
<!-- CMD-K -->
<div class="cmdk" id="cmdk">
<div class="cmdk-box">
<input class="cmdk-input" id="cmdk-input" placeholder="Rechercher partout (pages, API, agents, docs, KPIs)…" autocomplete="off">
<div class="cmdk-results" id="cmdk-results"></div>
<div class="cmdk-footer">
<span>↑↓ naviguer</span>
<span>↵ ouvrir</span>
<span>esc fermer</span>
</div>
</div>
</div>
<script>
const WTP_API = '/api/weval-technology-platform-api.php';
// Catégorie meta (icon + color css var)
const CAT = {
intelligence: {ic:'🧠', c:'var(--c-intel)'},
commerce: {ic:'💼', c:'var(--c-commerce)'},
finance: {ic:'💰', c:'var(--c-fin)'},
marketing: {ic:'📣', c:'var(--c-mkt)'},
growth: {ic:'📈', c:'var(--c-growth)'},
hr: {ic:'👥', c:'var(--c-hr)'},
supply: {ic:'📦', c:'var(--c-supply)'},
operations: {ic:'⚙️', c:'var(--c-ops)'},
erp_integrations:{ic:'🔌', c:'var(--c-erp)'},
communications:{ic:'💬', c:'var(--c-comm)'},
security: {ic:'🔒', c:'var(--c-sec)'},
development: {ic:'🛠️', c:'var(--c-dev)'},
knowledge: {ic:'📚', c:'var(--c-know)'},
multimodal: {ic:'🎨', c:'var(--c-multi)'},
rd: {ic:'🔬', c:'var(--c-rd)'},
lean6sigma: {ic:'🎯', c:'var(--c-l6s)'}
};
// Default favorites
const DEFAULT_FAVS = [
{id:'sales_hub', url:'/sales-hub.html', ic:'🚀', n:'Sales Hub', d:'Cockpit commercial'},
{id:'pitch', url:'/pitch.html', ic:'📄', n:'Pitch 1 écran', d:'Landing prospect'},
{id:'dg_cc', url:'/dg-command-center.html', ic:'🎖', n:'DG Center', d:'Pilotage direction'},
{id:'ent_cmp', url:'/enterprise-complete.html', ic:'🏢', n:'Enterprise', d:'20 depts · 169 KPIs'},
{id:'roi_sim', url:'/agent-roi-simulator.html', ic:'🧮', n:'ROI Simulator', d:'Discovery live'},
{id:'atlas', url:'/pain-points-atlas.html', ic:'🗺️', n:'Atlas', d:'35 pain points'},
{id:'intel', url:'/intelligence-growth.html', ic:'🌐', n:'Intel&Growth', d:'DarkScout · opps'},
{id:'wevia', url:'/wevia-master.html', ic:'🤖', n:'WEVIA Master', d:'Chat multi-agents'}
];
let DATA = null;
let FAVS = JSON.parse(localStorage.getItem('weval-favs') || JSON.stringify(DEFAULT_FAVS));
let currentFilter = 'all';
let currentView = 'grid';
function clockTick(){document.getElementById('clock').textContent = new Date().toLocaleTimeString('fr-FR') + ' · auto-refresh 60s';}
setInterval(clockTick,1000); clockTick();
async function load(){
try {
const r = await fetch(WTP_API+'?t='+Date.now());
DATA = await r.json();
render();
} catch(e) { console.error(e); }
}
function render(){
if(!DATA) return;
renderFavs();
renderCatRail();
renderGrid();
renderCompact();
}
function renderFavs(){
document.getElementById('quick-tiles').innerHTML = FAVS.map(f => `
<a class="qt" href="${f.url}" target="_blank" title="${f.d}">
<span class="ic">${f.ic}</span>
<div class="n">${f.n}</div>
<div class="d">${f.d}</div>
</a>
`).join('') + `<button class="qt qt-add" onclick="addFavorite()" title="Ajouter un favori">+</button>`;
}
function renderCatRail(){
const modules = DATA.modules || {};
const total = Object.values(modules).reduce((s,m)=>s+(m.submodules||[]).length, 0);
let html = `<button class="cat-btn ${currentFilter==='all'?'active':''}" onclick="setFilter('all')">Tous <span class="cnt">${total}</span></button>`;
for (const [mid, m] of Object.entries(modules)) {
const ic = (CAT[mid]||{}).ic || m.icon || '📁';
const n = m.label || mid;
const c = (m.submodules||[]).length;
html += `<button class="cat-btn ${currentFilter===mid?'active':''}" onclick="setFilter('${mid}')">${ic} ${n} <span class="cnt">${c}</span></button>`;
}
document.getElementById('cat-rail').innerHTML = html;
}
function setFilter(mid){
currentFilter = mid;
renderCatRail();
renderGrid();
renderCompact();
}
function renderGrid(){
if (currentView !== 'grid') { document.getElementById('grid-view').style.display='none'; return; }
document.getElementById('grid-view').style.display='block';
document.getElementById('compact-view').classList.remove('active');
const modules = DATA.modules || {};
let entries = Object.entries(modules);
if (currentFilter !== 'all') entries = entries.filter(([k]) => k === currentFilter);
let html = '';
for (const [mid, m] of entries) {
const ic = (CAT[mid]||{}).ic || m.icon || '📁';
const mc = (CAT[mid]||{}).c || 'var(--accent)';
const subs = m.submodules || [];
const open = currentFilter === mid || entries.length <= 4 ? 'open' : '';
html += `<div class="module-sec ${open}" data-mid="${mid}">
<div class="module-head ${open}" onclick="toggleModule('${mid}')">
<span class="chev">▸</span>
<span class="ic">${ic}</span>
<div class="info">
<div class="name">${m.label || mid}</div>
<div class="sub">${m.desc||m.description||''}</div>
</div>
<span class="cnt">${subs.length} sous-modules</span>
</div>
<div class="submod-grid" style="--mc:${mc}">
${subs.map(s => renderSubModule(s, mc, mid)).join('')}
</div>
</div>`;
}
document.getElementById('grid-view').innerHTML = html;
}
function renderSubModule(s, mc, mid) {
const pages = (s.pages||[]).slice(0,3);
const apis = (s.apis||[]).slice(0,3);
const scripts = (s.scripts||[]).slice(0,2);
const firstUrl = (s.pages||[])[0] || (s.apis||[])[0] || s.url || '#';
const openUrl = firstUrl && !firstUrl.startsWith('/') ? '/'+firstUrl : firstUrl;
const fav = FAVS.find(f=>f.id===s.id) ? 'on' : '';
const icMap = {crm:'🤝',sales:'💼',pipeline:'📊',candidates:'👥',agents:'🤖',roi:'🧮',atlas:'🗺️',offer:'💰',chatbot:'💬',pitch:'📄',hub:'🚀',board:'📋',market:'📈',insights:'🔍',growth:'🌱',finance:'💳',procure:'🛒',manuf:'🏭',quality:'✅',payroll:'💵',security:'🔒',legal:'⚖️'};
const sub_ic = s.icon || (Object.entries(icMap).find(([k])=>s.id.includes(k))||[,'📦'])[1];
const pagesCount = (s.pages||[]).length;
const apisCount = (s.apis||[]).length;
return `<div class="submod" style="--mc:${mc}" onclick="openSubModule('${openUrl}', event)">
<button class="submod-favorite ${fav}" onclick="event.stopPropagation();toggleFav('${s.id}', ${JSON.stringify({ic:sub_ic, n:s.label||s.id, d:(s.desc||'').substring(0,40), url:openUrl}).replace(/"/g,'&quot;')})">${fav?'★':'☆'}</button>
<div class="submod-body">
<div class="submod-head">
<div class="submod-ic">${sub_ic}</div>
<div>
<div class="submod-name">${s.label || s.id}</div>
<div class="submod-desc">${s.desc || s.description || ''}</div>
</div>
</div>
<div class="submod-kpi">
<div class="kpi-mini"><div class="v">${pagesCount}</div><div class="l">pages</div></div>
<div class="kpi-mini"><div class="v">${apisCount}</div><div class="l">apis</div></div>
<div class="kpi-mini"><div class="v">${scripts.length}</div><div class="l">scripts</div></div>
</div>
<div class="submod-pills">
${pages.map(p=>`<span class="pill page" title="${p}">${p.length>18?p.substring(0,16)+'…':p}</span>`).join('')}
${apis.slice(0,2).map(a=>`<span class="pill api" title="${a}">${(a.split('/').pop()||a).substring(0,18)}</span>`).join('')}
</div>
</div>
<div class="submod-foot">
<a class="submod-open" href="${openUrl}" target="_blank" onclick="event.stopPropagation()">→ Ouvrir</a>
<span class="submod-status live">live</span>
</div>
</div>`;
}
function renderCompact(){
if (currentView !== 'compact') { document.getElementById('compact-view').classList.remove('active'); return; }
document.getElementById('grid-view').style.display='none';
document.getElementById('compact-view').classList.add('active');
const modules = DATA.modules || {};
let entries = Object.entries(modules);
if (currentFilter !== 'all') entries = entries.filter(([k]) => k === currentFilter);
let html = '';
for (const [mid, m] of entries) {
for (const s of (m.submodules||[])) {
const ic = s.icon || '📦';
const firstUrl = (s.pages||[])[0] || (s.apis||[])[0] || '#';
const openUrl = firstUrl && !firstUrl.startsWith('/') ? '/'+firstUrl : firstUrl;
html += `<div class="compact-item" onclick="window.open('${openUrl}','_blank')">
<div class="c-ic">${ic}</div>
<div class="c-n">${s.label || s.id}</div>
<div class="c-d">${(s.desc||'').substring(0,80)}</div>
<div class="c-m">${(s.pages||[]).length}p · ${(s.apis||[]).length}a</div>
<div class="c-a">→</div>
</div>`;
}
}
document.getElementById('compact-view').innerHTML = html || '<div class="empty">Aucun résultat</div>';
}
function toggleModule(mid){
document.querySelectorAll(`.module-sec[data-mid="${mid}"]`).forEach(el => el.classList.toggle('open'));
document.querySelectorAll(`.module-sec[data-mid="${mid}"] .module-head`).forEach(el => el.classList.toggle('open'));
}
function openSubModule(url, e){ if (e.target.closest('.submod-favorite')) return; if (url && url !== '#') window.open(url, '_blank'); }
function toggleFav(id, meta){
const idx = FAVS.findIndex(f=>f.id===id);
if (idx >= 0) FAVS.splice(idx, 1);
else {
try {
const m = typeof meta === 'string' ? JSON.parse(meta.replace(/&quot;/g,'"')) : meta;
FAVS.push({id, ...m});
} catch(e){ console.error(e); }
}
localStorage.setItem('weval-favs', JSON.stringify(FAVS));
renderFavs();
renderGrid();
}
function addFavorite(){
document.getElementById('cmdk').classList.add('show');
document.getElementById('cmdk-input').focus();
}
// View switcher
document.querySelectorAll('.vt').forEach(b => b.onclick = () => {
document.querySelectorAll('.vt').forEach(x => x.classList.toggle('active', x===b));
currentView = b.dataset.view;
renderGrid(); renderCompact();
});
// Search top
document.getElementById('search').addEventListener('input', debounce(e => {
const q = e.target.value.toLowerCase().trim();
if (!q) { renderGrid(); renderCompact(); return; }
// Filter on submods only
const modules = DATA.modules || {};
let html = '';
for (const [mid, m] of Object.entries(modules)) {
const mc = (CAT[mid]||{}).c || 'var(--accent)';
const matches = (m.submodules||[]).filter(s => {
const hay = [s.label||'', s.desc||'', s.id||'', ...(s.pages||[]), ...(s.apis||[]), ...(s.scripts||[])].join(' ').toLowerCase();
return hay.includes(q);
});
if (matches.length === 0) continue;
html += `<div class="module-sec open" data-mid="${mid}">
<div class="module-head open"><span class="chev">▸</span><span class="ic">${(CAT[mid]||{}).ic||'📁'}</span>
<div class="info"><div class="name">${m.label}</div><div class="sub">${matches.length} match</div></div><span class="cnt">${matches.length}</span></div>
<div class="submod-grid" style="--mc:${mc}">${matches.map(s=>renderSubModule(s, mc, mid)).join('')}</div>
</div>`;
}
document.getElementById('grid-view').innerHTML = html || '<div class="empty">Aucun résultat pour « '+q+' »</div>';
}, 250));
function debounce(fn, t){let to; return (...a)=>{clearTimeout(to);to=setTimeout(()=>fn(...a),t)};}
// CMD-K
window.addEventListener('keydown', e => {
if ((e.metaKey||e.ctrlKey) && e.key.toLowerCase()==='k') {
e.preventDefault();
document.getElementById('cmdk').classList.add('show');
document.getElementById('cmdk-input').focus();
}
if (e.key === 'Escape') document.getElementById('cmdk').classList.remove('show');
});
document.getElementById('cmdk').addEventListener('click', e => { if (e.target.id==='cmdk') e.currentTarget.classList.remove('show'); });
document.getElementById('cmdk-input').addEventListener('input', e => {
const q = e.target.value.toLowerCase().trim();
const modules = DATA?.modules || {};
const items = [];
for (const [mid, m] of Object.entries(modules)) {
for (const s of (m.submodules||[])) {
const hay = [s.label||'', s.desc||'', s.id||'', ...(s.pages||[]), ...(s.apis||[])].join(' ').toLowerCase();
if (q === '' || hay.includes(q)) items.push({s, mid, m});
if (items.length >= 25) break;
}
if (items.length >= 25) break;
}
document.getElementById('cmdk-results').innerHTML = items.map(({s, mid, m}) => {
const url = (s.pages||[])[0] || (s.apis||[])[0] || '#';
const openUrl = url && !url.startsWith('/') ? '/'+url : url;
return `<div class="cmdk-item" onclick="window.open('${openUrl}','_blank');document.getElementById('cmdk').classList.remove('show')">
<span class="icon">${s.icon || (CAT[mid]||{}).ic || '📦'}</span>
<div class="txt"><div class="t-n">${s.label||s.id}</div><div class="t-m">${m.label} · ${openUrl}</div></div>
</div>`;
}).join('') || '<div class="empty" style="padding:30px">Aucun résultat</div>';
});
load();
setInterval(load, 60000);
</script>
<script>
/* V75 AVATAR UNIFIER — Meeting-rooms emoji style (Opus 19avr) */
(function() {
if (window.__WEVAL_AVATAR_V75) return;
window.__WEVAL_AVATAR_V75 = true;
const REG_URL = '/api/agent-avatars-v75.json';
const SVG_EP = '/api/agent-avatar-svg.php';
function emojiSVGUrl(name, emoji) {
return SVG_EP + '?n=' + encodeURIComponent(name) + '&e=' + encodeURIComponent(emoji);
}
fetch(REG_URL + '?t=' + Date.now()).then(r => r.json()).then(REG => {
function getAvatarUrl(name) {
const rec = REG[name];
if (!rec) return null;
if (typeof rec === 'object' && rec.svg) return rec.svg;
if (typeof rec === 'object' && rec.emoji) return emojiSVGUrl(name, rec.emoji);
return typeof rec === 'string' ? rec : null;
}
function findCI(key) {
const lower = key.toLowerCase();
for (const k of Object.keys(REG)) if (k.toLowerCase() === lower) return k;
return null;
}
function apply() {
document.querySelectorAll('img').forEach(img => {
const key = img.alt || img.dataset.agent || img.dataset.name || img.title || '';
if (!key) return;
let url = getAvatarUrl(key);
if (!url) { const alt = findCI(key); if (alt) url = getAvatarUrl(alt); }
if (url && img.src !== url && !img.src.endsWith(url)) {
img.src = url;
img.setAttribute('data-weval-v75', '1');
}
});
document.querySelectorAll('[data-agent]:not([data-weval-v75-applied])').forEach(el => {
const name = el.dataset.agent;
const url = getAvatarUrl(name);
if (!url) return;
const img = document.createElement('img');
img.src = url; img.alt = name; img.title = name;
img.className = 'v75-avatar';
img.style.cssText = 'width:32px;height:32px;border-radius:50%;object-fit:cover;vertical-align:middle;background:transparent';
el.setAttribute('data-weval-v75-applied', '1');
el.prepend(img);
});
}
apply();
setTimeout(apply, 400); setTimeout(apply, 1200); setTimeout(apply, 3000);
const mo = new MutationObserver(() => apply());
mo.observe(document.body, {childList: true, subtree: true});
setTimeout(() => mo.disconnect(), 20000);
console.log('[V75 AvatarUnifier] applied from', Object.keys(REG).length, 'agents');
}).catch(e => console.warn('[V75] fetch failed', e));
})();
</script>
<!-- === OPUS UNIVERSAL DRILL-DOWN v1 19avr — append-only, doctrine #14 === -->
<script>
(function(){
if (window.__opusUniversalDrill) return; window.__opusUniversalDrill = true;
var d = document;
var m = d.createElement('div');
m.id = 'opus-udrill';
m.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.82);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:99995;padding:20px;cursor:pointer';
var inner = d.createElement('div');
inner.id = 'opus-udrill-in';
inner.style.cssText = 'max-width:900px;width:100%;max-height:90vh;overflow:auto;background:#0b0d15;border:1px solid rgba(99,102,241,0.35);border-radius:14px;padding:28px;cursor:default;box-shadow:0 20px 60px rgba(0,0,0,0.6);color:#e2e8f0;font:14px/1.55 Inter,system-ui,sans-serif';
inner.addEventListener('click', function(e){ e.stopPropagation(); });
m.appendChild(inner);
m.addEventListener('click', function(){ m.style.display='none'; });
d.addEventListener('keydown', function(e){ if(e.key==='Escape') m.style.display='none'; });
(d.body || d.documentElement).appendChild(m);
function openCard(card) {
var html = '<div style="display:flex;justify-content:flex-end;margin-bottom:14px"><button id="opus-udrill-close" style="padding:6px 14px;background:#171b2a;border:1px solid rgba(99,102,241,0.25);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:12px">✕ Fermer (Esc)</button></div>';
html += '<div style="transform-origin:top left;font-size:1.05em">' + card.outerHTML + '</div>';
inner.innerHTML = html;
d.getElementById('opus-udrill-close').onclick = function(){ m.style.display='none'; };
m.style.display = 'flex';
}
function wire(root) {
var sels = '.card,[class*="card"],.kpi,[class*="kpi"],.stat,[class*="stat"],.tile,[class*="tile"],.metric,[class*="metric"],.widget,[class*="widget"]';
var cards = root.querySelectorAll(sels);
for (var i = 0; i < cards.length; i++) {
var c = cards[i];
if (c.__opusWired) continue;
if (c.closest('button, a, input, select, textarea, #opus-udrill')) continue;
var r = c.getBoundingClientRect();
if (r.width < 60 || r.height < 40) continue;
c.__opusWired = true;
c.style.cursor = 'pointer';
c.setAttribute('role','button');
c.setAttribute('tabindex','0');
c.addEventListener('click', function(ev){
if (ev.target.closest('[data-pp-id]') && window.__opusDrillInit) return;
if (ev.target.closest('a,button,input,select')) return;
ev.preventDefault(); ev.stopPropagation();
openCard(this);
});
c.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();openCard(this);} });
}
}
var initRun = function(){ wire(d.body || d.documentElement); };
if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', initRun);
else initRun();
var mo = new MutationObserver(function(muts){
var newCard = false;
for (var i=0;i<muts.length;i++) if (muts[i].addedNodes.length) { newCard = true; break; }
if (newCard) initRun();
});
mo.observe(d.body || d.documentElement, {childList:true, subtree:true});
})();
</script>
<!-- === OPUS UNIVERSAL DRILL-DOWN END === -->
</body>
</html>