Files
html/orphans-dashboard.html
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

271 lines
14 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" data-theme="dark">
<head>
<meta charset="UTF-8">
<title>Orphans Dashboard — Doctrine 92 Hub Merged</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
:root{--bg:#05060a;--bg2:#0e111c;--bg3:#181c2b;--br:#1f2436;--tx:#f1f5f9;--tx2:#94a3b8;--ac:#6366f1;--green:#22c55e;--yellow:#fbbf24;--red:#ef4444}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:system-ui,sans-serif;background:var(--bg);color:var(--tx);padding:24px;line-height:1.5}
h1{font-size:30px;font-weight:800;background:linear-gradient(135deg,#6366f1,#ec4899);-webkit-background-clip:text;color:transparent;margin-bottom:4px}
.sub{color:var(--tx2);margin-bottom:20px;font-size:14px}
.back{display:inline-block;padding:8px 16px;background:var(--bg3);border:1px solid var(--br);border-radius:8px;color:var(--tx);text-decoration:none;margin-bottom:20px;transition:.2s}
.back:hover{border-color:var(--ac);transform:translateX(-2px)}
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;margin-bottom:24px}
.stat{background:var(--bg2);border:1px solid var(--br);border-radius:12px;padding:16px;transition:.2s}
.stat:hover{border-color:var(--ac);transform:translateY(-2px)}
.stat.active{border-color:var(--green)}
.stat.dormant{border-color:var(--yellow)}
.stat.archive{border-color:var(--tx2)}
.stat .n{font-size:28px;font-weight:800;color:var(--ac);margin-bottom:4px}
.stat.active .n{color:var(--green)}
.stat.dormant .n{color:var(--yellow)}
.stat.archive .n{color:var(--tx2)}
.stat .l{font-size:11px;color:var(--tx2);text-transform:uppercase;letter-spacing:.5px}
.tabs{display:flex;gap:8px;margin-bottom:20px;border-bottom:1px solid var(--br)}
.tab{padding:10px 20px;background:none;border:none;color:var(--tx2);cursor:pointer;font-weight:600;font-size:14px;border-bottom:2px solid transparent;transition:.2s}
.tab.active{color:var(--ac);border-bottom-color:var(--ac)}
.tab:hover{color:var(--tx)}
.tab .count{display:inline-block;background:var(--bg3);padding:2px 8px;border-radius:10px;font-size:11px;margin-left:6px}
.filter{margin-bottom:16px;display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.filter input{background:var(--bg2);border:1px solid var(--br);border-radius:8px;padding:10px 14px;color:var(--tx);font-size:14px;min-width:240px}
.filter input:focus{outline:none;border-color:var(--ac)}
.filter select{background:var(--bg2);border:1px solid var(--br);border-radius:8px;padding:10px 14px;color:var(--tx);font-size:14px;cursor:pointer}
.pages{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:10px}
.page{display:block;padding:12px 14px;background:var(--bg2);border:1px solid var(--br);border-radius:10px;color:var(--tx);text-decoration:none;transition:.2s;position:relative}
.page:hover{border-color:var(--ac);transform:translateY(-1px);box-shadow:0 4px 12px rgba(99,102,241,0.2)}
.page-header{display:flex;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:6px}
.pn{font-family:'SF Mono',Menlo,monospace;font-size:12px;color:var(--ac);font-weight:600;word-break:break-all}
.page.active .pn{color:var(--green)}
.page.dormant .pn{color:var(--yellow)}
.page.archive .pn{color:var(--tx2)}
.pt{font-size:13px;color:var(--tx);margin-bottom:4px;font-weight:500}
.pm{font-size:11px;color:var(--tx2);display:flex;gap:10px;flex-wrap:wrap}
.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.3px}
.badge.active{background:rgba(34,197,94,0.2);color:var(--green);border:1px solid var(--green)}
.badge.dormant{background:rgba(251,191,36,0.2);color:var(--yellow);border:1px solid var(--yellow)}
.badge.archive{background:rgba(148,163,184,0.2);color:var(--tx2);border:1px solid var(--tx2)}
.badge.suite{background:var(--bg3);color:var(--tx2);border:1px solid var(--br)}
.empty{text-align:center;padding:40px;color:var(--tx2)}
.loading{text-align:center;padding:40px;color:var(--ac)}
.snippet-btn{display:inline-block;margin-top:10px;padding:10px 16px;background:linear-gradient(135deg,#6366f1,#ec4899);color:#fff;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;border:none}
.snippet-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(99,102,241,0.4)}
#snippet-modal{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.8);z-index:100;padding:40px;overflow:auto}
#snippet-modal.show{display:block}
.modal-content{background:var(--bg2);border:1px solid var(--br);border-radius:12px;padding:24px;max-width:900px;margin:0 auto}
.modal-content pre{background:#000;border:1px solid var(--br);border-radius:8px;padding:14px;overflow:auto;font-size:12px;color:var(--green);max-height:500px}
.modal-close{float:right;background:none;border:none;color:var(--tx);font-size:24px;cursor:pointer}
/* === 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>
<a href="/weval-technology-platform.html" class="back">← WTP</a>
<h1>Orphans Dashboard</h1>
<div class="sub">Doctrine 92 · Hub Merged · V82 suites métier + D91 classifier</div>
<div class="stats" id="stats"></div>
<div class="tabs">
<button class="tab active" data-filter="all">Tous <span class="count" id="c-all">·</span></button>
<button class="tab" data-filter="ACTIVE">Actifs à rebrancher <span class="count" id="c-active">·</span></button>
<button class="tab" data-filter="DORMANT">Dormant <span class="count" id="c-dormant">·</span></button>
<button class="tab" data-filter="ARCHIVE">Archive <span class="count" id="c-archive">·</span></button>
</div>
<div class="filter">
<input type="text" id="search" placeholder="Rechercher page, titre ou suite métier…">
<select id="suite-filter"><option value="">Toutes suites</option></select>
<button class="snippet-btn" onclick="showSnippet()">📋 Voir snippet HTML WTP-ready</button>
</div>
<div id="pages" class="pages"><div class="loading">Chargement…</div></div>
<div id="snippet-modal">
<div class="modal-content">
<button class="modal-close" onclick="hideSnippet()">×</button>
<h2>Snippet HTML prêt à injecter dans WTP</h2>
<p class="sub">Additive pur · avant &lt;/body&gt; · pattern V80 · GOLD backup obligatoire</p>
<pre id="snippet-code">Chargement…</pre>
<button class="snippet-btn" onclick="copySnippet()">Copier</button>
</div>
</div>
<script>
let DATA = null;
let CURRENT_FILTER = 'all';
let CURRENT_SUITE = '';
let SEARCH_TERM = '';
async function load() {
try {
const r = await fetch('/api/opus5-orphans-hub.php');
DATA = await r.json();
renderStats();
renderSuiteFilter();
render();
} catch (e) {
document.getElementById('pages').innerHTML = '<div class="empty">Erreur chargement : ' + e.message + '</div>';
}
}
function renderStats() {
const s = DATA.stats;
document.getElementById('stats').innerHTML = `
<div class="stat"><div class="n">${s.total_orphans}</div><div class="l">Total orphelins</div></div>
<div class="stat active"><div class="n">${s.active_to_link}</div><div class="l">Actifs à rebrancher</div></div>
<div class="stat dormant"><div class="n">${s.dormant_candidate}</div><div class="l">Dormant (user decide)</div></div>
<div class="stat archive"><div class="n">${s.archive_legit}</div><div class="l">Archive légitime</div></div>
<div class="stat"><div class="n">${s.suites_v82}</div><div class="l">Suites métier V82</div></div>
<div class="stat"><div class="n">${s.sync_ok ? '✓' : '≠'}</div><div class="l">V82↔D91 sync</div></div>
`;
document.getElementById('c-all').textContent = s.total_orphans;
document.getElementById('c-active').textContent = s.active_to_link;
document.getElementById('c-dormant').textContent = s.dormant_candidate;
document.getElementById('c-archive').textContent = s.archive_legit;
}
function renderSuiteFilter() {
const suites = [...new Set(DATA.merged.map(m => m.suite_v82))].sort();
const sel = document.getElementById('suite-filter');
suites.forEach(s => sel.innerHTML += `<option value="${s}">${s}</option>`);
}
function render() {
let filtered = DATA.merged;
if (CURRENT_FILTER !== 'all') filtered = filtered.filter(m => m.classif_d91 === CURRENT_FILTER);
if (CURRENT_SUITE) filtered = filtered.filter(m => m.suite_v82 === CURRENT_SUITE);
if (SEARCH_TERM) {
const q = SEARCH_TERM.toLowerCase();
filtered = filtered.filter(m =>
m.page.toLowerCase().includes(q) ||
(m.title||'').toLowerCase().includes(q) ||
(m.suite_v82||'').toLowerCase().includes(q)
);
}
const container = document.getElementById('pages');
if (filtered.length === 0) {
container.innerHTML = '<div class="empty">Aucun orphelin pour ce filtre</div>';
return;
}
container.innerHTML = filtered.map(m => {
const cls = m.classif_d91.toLowerCase();
return `
<a href="${m.url}" class="page ${cls}" target="_blank">
<div class="page-header">
<div class="pn">${m.page}</div>
<span class="badge ${cls}">${m.classif_d91}</span>
</div>
<div class="pt">${m.title || '(sans titre)'}</div>
<div class="pm">
<span class="badge suite">${m.suite_v82}</span>
<span>${m.size_kb} KB</span>
<span>${m.class}</span>
${m.reason ? '<span>· '+m.reason+'</span>' : ''}
</div>
</a>
`;
}).join('');
}
document.querySelectorAll('.tab').forEach(t => t.onclick = () => {
document.querySelectorAll('.tab').forEach(x => x.classList.remove('active'));
t.classList.add('active');
CURRENT_FILTER = t.dataset.filter;
render();
});
document.getElementById('search').oninput = e => { SEARCH_TERM = e.target.value; render(); };
document.getElementById('suite-filter').onchange = e => { CURRENT_SUITE = e.target.value; render(); };
function showSnippet() {
document.getElementById('snippet-modal').classList.add('show');
document.getElementById('snippet-code').textContent = DATA.snippet_html || '(empty)';
}
function hideSnippet() { document.getElementById('snippet-modal').classList.remove('show'); }
function copySnippet() {
navigator.clipboard.writeText(DATA.snippet_html);
alert('Snippet copié — paste avant
<!-- === 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> dans WTP');
}
load();
</script>
<script src="/api/archi-meta-badge.js" defer></script>
</body></html>