neuronal / scripts /scansione.html
amogaddy's picture
amogaddyofficial
claude sonnet 4.6
Neuronal
d601f7c
Raw History Blame Contribute Delete
8.36 kB
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Scansione dei livelli</title>
<style>
:root {
--bg: #0b0f14; --panel: #131a22; --line: #24303c; --text: #e6edf3; --muted: #8b9aa9;
--accent: #3ddc97; --warn: #f2c14e; --bad: #ef6461; --run: #5aa9ff;
}
@media (prefers-color-scheme: light) {
:root:not([data-theme="dark"]) { --bg: #f4f6f8; --panel: #fff; --line: #d8dee4; --text: #1b2733; --muted: #5f6f7e; --accent: #1f9d68; --warn: #b7791f; --bad: #c53030; --run: #2b6cb0; }
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--text); font: 15px/1.5 system-ui, "Segoe UI", sans-serif; }
main { max-width: 980px; margin: 0 auto; padding: 24px 16px 48px; }
h1 { font-size: 22px; margin: 0 0 4px; letter-spacing: .02em; }
.sub { color: var(--muted); margin: 0 0 20px; }
.card { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 16px; margin-bottom: 16px; }
.row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; justify-content: space-between; }
.msg { font-weight: 600; }
.bar { height: 10px; background: var(--line); border-radius: 6px; overflow: hidden; margin: 12px 0 6px; }
.bar > i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .4s; }
.bar.q > i { background: var(--run); }
.nums { display: flex; gap: 20px; flex-wrap: wrap; color: var(--muted); font-size: 13px; }
.nums b { color: var(--text); font-variant-numeric: tabular-nums; }
.grid { display: grid; grid-template-columns: repeat(16, 1fr); gap: 5px; }
.cell { aspect-ratio: 1; border-radius: 6px; background: var(--line); display: flex; align-items: center; justify-content: center;
font-size: 11px; color: var(--muted); position: relative; }
.cell.done { color: #0b0f14; font-weight: 700; }
.cell.skip { opacity: .35; }
.cell.now { outline: 2px solid var(--run); color: var(--text); animation: pulso 1.1s ease-in-out infinite; }
@keyframes pulso { 50% { outline-color: transparent; } }
.legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--muted); margin-top: 10px; }
.legend span::before { content: ""; display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; background: var(--c); }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--line); }
th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .05em; }
td.n { font-variant-numeric: tabular-nums; }
td.ex { color: var(--muted); max-width: 360px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mini { display: inline-block; height: 8px; border-radius: 4px; vertical-align: middle; }
button { font: inherit; background: var(--panel); color: var(--text); border: 1px solid var(--line); border-radius: 8px; padding: 8px 14px; cursor: pointer; }
button:hover:not(:disabled) { border-color: var(--bad); color: var(--bad); }
button:disabled { opacity: .5; cursor: default; }
.note { color: var(--muted); font-size: 13px; }
.good { color: var(--accent); }
</style>
</head>
<body>
<main>
<h1>Scansione dei livelli</h1>
<p class="sub">Spegne un livello alla volta del cervello e misura quanto cambiano le risposte. Tutto sul tuo computer.</p>
<section class="card">
<div class="row">
<div class="msg" id="msg">Avvio…</div>
<button id="stop" type="button">Ferma (salva il lavoro)</button>
</div>
<div class="bar"><i id="bar-tot"></i></div>
<div class="nums">
<span>Livelli <b id="n-fatti">0</b> / <b id="n-tot">0</b></span>
<span>Trascorso <b id="t-trasc">0 s</b></span>
<span>Mancano circa <b id="t-manca">—</b></span>
<span>Livello <b id="n-liv">—</b></span>
</div>
<div class="bar q"><i id="bar-q"></i></div>
<div class="nums"><span>Domanda <b id="n-q">0</b> / <b id="n-qt">0</b></span></div>
</section>
<section class="card">
<div class="grid" id="grid"></div>
<div class="legend">
<span style="--c:var(--accent)">si può togliere (≥ 0.90)</span>
<span style="--c:var(--warn)">costa un po' (0.60–0.90)</span>
<span style="--c:var(--bad)">importante (&lt; 0.60)</span>
<span style="--c:var(--run)">in corso</span>
</div>
</section>
<section class="card">
<table>
<thead><tr><th>Livello</th><th>Punteggio</th><th></th><th>Identiche</th><th>Prima risposta senza il livello</th></tr></thead>
<tbody id="tab"></tbody>
</table>
<p class="note" id="esito"></p>
<p class="note" id="nota">Qui si spegne solo la parte FFN del livello. Togliere il livello intero (anche l'attenzione) costa uguale o di più:
la tabella è un limite ottimistico, da riverificare su un modello davvero accorciato.</p>
</section>
</main>
<script>
const $ = (id) => document.getElementById(id);
const fmt = (s) => s == null ? '—' : s < 60 ? `${Math.round(s)} s` : s < 3600 ? `${Math.floor(s / 60)} min ${String(Math.round(s % 60)).padStart(2, '0')} s` : `${Math.floor(s / 3600)} h ${String(Math.floor(s % 3600 / 60)).padStart(2, '0')} min`;
const colore = (p) => p >= 0.9 ? 'var(--accent)' : p >= 0.6 ? 'var(--warn)' : 'var(--bad)';
function disegna(s) {
$('msg').textContent = s.messaggio;
const fatti = s.tutti.filter((l) => String(l) in s.livelli).length;
$('n-fatti').textContent = fatti; $('n-tot').textContent = s.tutti.length;
$('bar-tot').style.width = (s.tutti.length ? 100 * fatti / s.tutti.length : 0) + '%';
$('t-trasc').textContent = fmt(s.trascorso);
$('t-manca').textContent = s.fase === 'scansione' ? fmt(s.mancano) : '—';
$('n-liv').textContent = s.livello == null ? '—' : s.livello;
$('n-q').textContent = s.domanda; $('n-qt').textContent = s.domande;
$('bar-q').style.width = (s.domande ? 100 * s.domanda / s.domande : 0) + '%';
$('stop').disabled = s.finito || s.ferma || s.fase === 'finito' || s.fase === 'fermato' || s.fase === 'errore';
const grid = $('grid');
const celle = Array.from({ length: 64 }, (_, i) => i);
if (grid.children.length !== 64) {
grid.replaceChildren(...celle.map((i) => { const d = document.createElement('div'); d.textContent = i; return d; }));
}
celle.forEach((i) => {
const c = grid.children[i], r = s.livelli[String(i)], inLista = s.tutti.includes(i);
c.className = 'cell' + (r ? ' done' : '') + (!inLista ? ' skip' : '') + (s.livello === i ? ' now' : '');
c.style.background = r ? colore(r.punteggio) : '';
c.title = r ? `livello ${i}: punteggio ${r.punteggio.toFixed(2)}` : `livello ${i}`;
});
const righe = Object.entries(s.livelli).sort((a, b) => b[1].punteggio - a[1].punteggio);
$('tab').replaceChildren(...righe.map(([l, r]) => {
const tr = document.createElement('tr');
const cella = (t, cls) => { const td = document.createElement('td'); if (cls) td.className = cls; td.textContent = t; return td; };
const barra = document.createElement('td');
const m = document.createElement('i'); m.className = 'mini'; m.style.width = Math.round(r.punteggio * 90) + 'px'; m.style.background = colore(r.punteggio);
barra.append(m);
tr.append(cella(l, 'n'), cella(r.punteggio.toFixed(2), 'n'), barra, cella(`${r.identiche} / ${s.domande}`, 'n'), cella(r.esempio || '', 'ex'));
return tr;
}));
if (s.fase === 'finito') {
const ok = righe.filter(([, r]) => r.punteggio >= 0.9).map(([l]) => Number(l)).sort((a, b) => a - b);
$('esito').innerHTML = `<b class="good">Candidati da togliere (≥ 0.90):</b> ${ok.length ? ok.join(', ') : 'nessuno'}.`;
}
}
async function giro() {
try { disegna(await (await fetch('/stato.json', { cache: 'no-store' })).json()); }
catch { $('msg').textContent = 'La scansione si è chiusa. L\'ultimo risultato è nel file state/scansione-ablazione.json.'; $('stop').disabled = true; }
setTimeout(giro, 1000);
}
$('stop').onclick = () => { $('stop').disabled = true; fetch('/ferma', { method: 'POST' }); };
giro();
</script>
</body>
</html>