Files
html/api/archi-spotlight.js
opus e30ddf5007
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
auto-sync via WEVIA git_sync_all intent 2026-04-20T13:11:38+02:00
2026-04-20 13:11:38 +02:00

111 lines
5.7 KiB
JavaScript

// =============================================================
// WEVAL ARCHI SPOTLIGHT V83 · Quick jumper · Ctrl+K / Cmd+K
// Cherche dans 280 pages / 730 APIs / 4 CRMs / 906 agents
// =============================================================
(function(){
if (window.__WEVAL_SPOTLIGHT_LOADED) return;
window.__WEVAL_SPOTLIGHT_LOADED = true;
const style = document.createElement('style');
style.textContent = `
#wevsp-backdrop {
position: fixed; inset: 0; z-index: 99997;
background: rgba(15,23,42,.65);
backdrop-filter: blur(8px);
display: none; align-items: flex-start; justify-content: center; padding-top: 12vh;
}
#wevsp-backdrop.open { display: flex; animation: wevsp-fade .15s; }
@keyframes wevsp-fade { from{opacity:0} to{opacity:1} }
#wevsp-box {
width: min(640px, 92vw); background: rgba(15,23,42,.98);
border-radius: 14px; border: 1px solid rgba(148,163,184,.25);
box-shadow: 0 24px 64px rgba(0,0,0,.6);
overflow: hidden; font: 400 14px/1.4 system-ui, sans-serif;
}
#wevsp-input { width:100%; padding: 16px 20px; background: transparent;
border: 0; border-bottom: 1px solid rgba(148,163,184,.15);
color: #e2e8f0; font-size: 16px; outline: none; font-weight: 500;
}
#wevsp-input::placeholder { color: #64748b; }
#wevsp-results { max-height: 55vh; overflow-y: auto; padding: 6px 0; }
.wevsp-row { display:flex; align-items:center; gap:12px; padding: 10px 20px; cursor: pointer; color:#cbd5e1; }
.wevsp-row:hover, .wevsp-row.active { background: rgba(20,184,166,0.10); color: #5eead4; }
.wevsp-row .emo { font-size: 18px; width: 22px; text-align:center; }
.wevsp-row .name { font-weight: 600; flex: 1; }
.wevsp-row .role { font-size: 11px; color: #94a3b8; margin-top: 2px; }
.wevsp-row .cat { font-size: 10px; padding: 2px 8px; border-radius: 10px; background: rgba(148,163,184,0.12); color: #94a3b8; text-transform: uppercase; letter-spacing:.5px; }
#wevsp-foot { padding: 10px 20px; font-size: 11px; color: #64748b; border-top: 1px solid rgba(148,163,184,.12); display:flex; justify-content: space-between;}
#wevsp-foot kbd { background: rgba(148,163,184,.15); padding: 2px 6px; border-radius: 4px; font-family: inherit; }
`;
document.head.appendChild(style);
const wrap = document.createElement('div');
wrap.id = 'wevsp-backdrop';
wrap.innerHTML = `<div id="wevsp-box">
<input id="wevsp-input" placeholder="🔍 Spotlight · cherche dashboard, CRM, doctrine, doc…" autocomplete="off" />
<div id="wevsp-results"></div>
<div id="wevsp-foot"><span>↑↓ naviguer · Enter ouvrir · Esc fermer</span><span><kbd>Ctrl</kbd>+<kbd>K</kbd> pour ouvrir</span></div>
</div>`;
document.body.appendChild(wrap);
let items = [];
let filtered = [];
let activeIdx = 0;
async function load() {
try {
const r = await fetch('/api/weval-archi-manifest.php?t='+Date.now(), {cache:'no-store'});
const d = await r.json();
items = [];
const emojis = {canonical:'⭐', business:'💼', crm:'🔗', ia:'🧠', infra:'🏗️', pharma:'⚕️', email:'📧'};
Object.entries(d.dashboards).forEach(([cat, list]) => {
list.forEach(x => items.push({name: x.name, url: x.url, role: x.role||'', cat, emo: emojis[cat]||'📄'}));
});
// Add servers as searchable
(d.servers||[]).forEach(s => items.push({name: s.name, url: '#', role: s.role + ' · ' + s.ip, cat: 'infra', emo: '🖥️'}));
// Add CRMs
(d.crms||[]).forEach(c => items.push({name: c.name, url: c.url, role: c.role||'', cat: 'crm', emo: '🔗'}));
} catch(e) { console.error('[spotlight]', e); }
}
function render(query) {
const q = (query||'').trim().toLowerCase();
filtered = q ? items.filter(x => (x.name+' '+x.role+' '+x.cat).toLowerCase().includes(q)) : items;
activeIdx = 0;
const html = filtered.slice(0, 30).map((x, i) =>
`<div class="wevsp-row${i===activeIdx?' active':''}" data-idx="${i}">
<span class="emo">${x.emo}</span>
<div style="flex:1"><div class="name">${x.name}</div>${x.role?`<div class="role">${x.role}</div>`:''}</div>
<span class="cat">${x.cat}</span>
</div>`
).join('');
document.getElementById('wevsp-results').innerHTML = html || '<div style="padding:30px;text-align:center;color:#64748b">Aucun résultat</div>';
}
function open() { wrap.classList.add('open'); document.getElementById('wevsp-input').focus(); document.getElementById('wevsp-input').value=''; render(''); }
function close() { wrap.classList.remove('open'); }
function navigate(idx) { if (!filtered[idx]) return; if (filtered[idx].url && filtered[idx].url !== '#') window.open(filtered[idx].url, '_blank'); close(); }
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
e.preventDefault();
open();
return;
}
if (!wrap.classList.contains('open')) return;
if (e.key === 'Escape') close();
if (e.key === 'ArrowDown') { activeIdx = Math.min(activeIdx+1, filtered.length-1); render(document.getElementById('wevsp-input').value); e.preventDefault(); }
if (e.key === 'ArrowUp') { activeIdx = Math.max(0, activeIdx-1); render(document.getElementById('wevsp-input').value); e.preventDefault(); }
if (e.key === 'Enter') { navigate(activeIdx); e.preventDefault(); }
});
wrap.addEventListener('click', (e) => { if (e.target === wrap) close(); });
document.getElementById('wevsp-input').addEventListener('input', (e) => render(e.target.value));
document.getElementById('wevsp-results').addEventListener('click', (e) => {
const row = e.target.closest('.wevsp-row');
if (row) navigate(parseInt(row.dataset.idx));
});
load();
})();