Novità

Vi svelerò...

Allora non capisco gli altri come ricavarli

L'ho spiegato come me l'ha spiegato a me Qwen qui ma a parte i punti 3 e 4 che praticamente non sono serviti e sono i meno chiari in assoluto essenzialmente nel caso winner (al 80% si sarà trattato comunque di un bel ( . ) :) ) io ho fatto così...

1) preso l'ultima estrazione di BA (prima ruota che aveva almeno 2 elementi in decina)
2) ho posizionato a nord del cerchio (a proposito scaricate e usate la v2 che è na 💣 rispetto la v1) il primo numero dell'estrazione di BA e importante ho fatto collimare il conoide sui tre cerchi in modo da formare appunto il conoide guida...
3) ho verificato che il numero a nord avesse almeno un altro numero degli estratti in un punto cardinale rimanente
4) una volta verificato ciò il cerchio secondo QWEN si poteva "attivare" ossia le due condizioni richieste erano state rispettate.
5) semplicemente poi ho preso e unito tutti i numeri "non usciti" degli spicchi lungo l'asse dei cardinali N S e E O ottenendo 8 numeri . A questi ho aggiunto quelli del punto 4 che però almeno in quell'occasione non sono serviti minimamente.... ottenendo il gruppo unione di 11 numeri che ha sfaldato a colpo il terno. Per la c5 ci ho messo del mio.. nel senso che ho semplicemente analizzato la c11 per s1 n c1 su BA by 1871 e ordinato i risultati per fq decrescente. Poi ho preso e unito le prime due fq max uniche (IL SOPRA..) le due fq min uniche (IL SOTTO...) e il centrale (IN MEDIA STAT VIRTUS che comunque non è servito...)

Tutto qui... 🫠

BA ha di nuovo entrambe le condizioni per attivare il cerchio...

rifattoalvoloconlav2xBAdiadesso.jpg


In verde quelli dell'estrazione di questa sera. (deve essere sempre la + recente...)

in giallo quelli sugli assi cardinali ottenuti dopo aver messo il primo numero (76 a nord) , e ricomposto il conoide guida...

la doppia condizione per attivare il cerchio è rispettata anche in questo caso perchè:

A) ci sono due elementi in decina (76 e 75)
B) dopo aver messo il primo numero a nord, dopo aver quindi formato il conoide il 20 che è un altro degli estratti dell'estrazione base, è su uno dei punti cardinali (O).

La previsione sarebbe quella formata dai numeri gialli e volendo anche quelli verdi sempre sugli assi quindi in tutto sarebbero : 16-46-12-42-72-80-50 + opzionali... (76 e 20)

Interessante anche il fatto che i colpi di attesa per questo tipo particolarissimo di predizione alla Indiana Jones... 🤠 sarebbero sempre 1 perchè basati appunto solo sull'estrazione precedente... :unsure:

Questo ovviamente è solo un esempio fresco fresco per cercare di spiegare quello che ha spiegato a me QWEN riguardo il cerchio che avevo costruito in flash nel 2013 e che poi Claude AI mi ha evoluto in 15 min scarsi a questa versione v2 in html :)

Nessuna Certezza Solo Poca Probabilità
 
Ultima modifica:
Grazie ,Domani ci guardo con calma .
Per avere 2 numeri nei punti cardinali serve una distanza 18 , questo facendo girare nel primo cerchio flash .
Ma ora ho capito metti il primo estratto sul angolo nord e allinei i cerchi e se altro numero va su cardinale è giocabile .
 
Ultima modifica:
Grazie ,Domani ci guardo con calma .
Per avere 2 numeri nei punti cardinali serve una distanza 18 , questo facendo girare nel primo cerchio flash .
Ma ora ho capito metti il primo estratto sul angolo nord e allinei i cerchi e se altro numero va su cardinale è giocabile .

Si.. Ma usa la v2 in HTML non la vecchia v flash. E' troppo meglio la v2 che mi ha costruito Claude AI appena ieri mattina in 15 min scarsi mentre ancora stavo a letto 😁
 
...si aspetta questo 67 con ansia --:unsure: chissà fussechefusse...mi sono lasciato andare ad una versione fantasiosa ;)

Y3AzZrKfit.png
 
Ultima modifica:
da quello che ho capito io , deve uscire , non a colpo, o il 51 o il 21 a Bari e 35 e 43 come abbinamenti , quindi 21 35 43 51 per ambo altri 6 concorsi di gioco
metodo: prendo il 29 primo estratto lo posiziono al nord di rimpetto cerco l'ambata futura e trovo il 21, allinea la ghiera piu' piccola interna, trovo il 51, se guarda ad est trovo nel punto degli angoli del rombo interno, trovo 35 e 43 ad ovest. terzine simmetriche mancanti di elementi (77) e (85). di piu' non so fare!


basta guardare cosa avrebbe dato a tutte nel frattempo.!
 

Allegati

  • Screenshot 2026-09-05 172203.png
    Screenshot 2026-09-05 172203.png
    183,4 KB · Visite: 10
  • Screenshot 2026-09-05 172238.png
    Screenshot 2026-09-05 172238.png
    308,2 KB · Visite: 12
Ultima modifica:
per firenze e nazionale 4 terzine simmetriche per ambo


validità i vecchi 9 concorsi

ambata 31 fi/nz

oppure per ambo 01 31 61 - 09 39 69 - 13 43 73 - 05 35 65
 

Allegati

  • Screenshot 2026-09-05 175407.png
    Screenshot 2026-09-05 175407.png
    423,5 KB · Visite: 11
ho voluto assieme alla mia grandiosa compagna di avventura QwentheQwen se non la nomino si offende... aggiungere già al lavoro straordinario di @lotto_tom75 qualche inserimento ,in allegato trovate anche una guida all'utilizzo sempre in html. ovviamente da txt a html .Un saluto a tutti se avete voglia di condividere cose che agli altri sfuggono ben vengano si fa tutti un unica giocata...;)


HTML:
<!DOCTYPE html>
<html lang="it" data-theme="dark">
<head>
<meta charset="UTF-8">
<title>Cerchio di Spilimbergo — Suite Completa v2.2</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<style>
  @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;600;700&family=Inter:wght@400;500;600&display=swap');

  :root[data-theme="dark"]{
    --frame-dark:#241a10; --frame-dark2:#1a130b;
    --brass:#b4842c; --brass-hi:#e0b04a;
    --paper-1:#f4ecd8; --ink:#2c1e12; --ink-soft:#5a4630;
    --garnet:#8c2a2a; --accent:#0f6e6e; --gold-fill:#e0b04a;
    --panel-bg:linear-gradient(180deg,#2b1f12,#201709);
    --body-bg:radial-gradient(ellipse at 50% -10%, #2c2013 0%, #1a130b 55%);
    --input-bg:#171009;
  }
  :root[data-theme="light"]{
    --frame-dark:#e8dcc4; --frame-dark2:#d4c5a0;
    --brass:#8b6914; --brass-hi:#a67c1a;
    --paper-1:#2c1e12; --ink:#f4ecd8; --ink-soft:#8a7a5c;
    --garnet:#b23a3a; --accent:#1a8f8f; --gold-fill:#d4a017;
    --panel-bg:linear-gradient(180deg,#f5edd8,#e8dcc4);
    --body-bg:radial-gradient(ellipse at 50% -10%, #f5edd8 0%, #d4c5a0 55%);
    --input-bg:#faf6eb;
  }
  *{box-sizing:border-box;}
  html,body{margin:0;padding:0;min-height:100%;background:var(--body-bg);color:var(--paper-1);
    font-family:'Inter',sans-serif;transition:background .4s,color .4s;}
  h1{font-family:'Cormorant Garamond',serif;font-weight:700;font-size:clamp(26px,4vw,40px);margin:0 0 2px;color:var(--brass-hi);}
  .subtitle{font-family:'Cormorant Garamond',serif;font-style:italic;color:#c9b98f;font-size:16px;margin:0 0 20px;}
  .wrap{max-width:1320px;margin:0 auto;padding:28px 16px 56px;}
  .toolbar{display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap;justify-content:center;}
  .toolbar button{background:var(--panel-bg);border:1px solid var(--brass);color:var(--paper-1);
    font-family:'Inter',sans-serif;font-size:13px;padding:8px 14px;border-radius:8px;cursor:pointer;transition:all .2s;}
  .toolbar button:hover{border-color:var(--brass-hi);transform:translateY(-1px);}
  .layout{display:grid;grid-template-columns:minmax(300px,560px) 360px;gap:24px;align-items:start;}
  @media (max-width:980px){.layout{grid-template-columns:1fr;}}
  .wheel-card{position:relative;background:var(--panel-bg);border:1px solid #4a3a20;border-radius:16px;
    padding:10px;box-shadow:0 30px 60px -20px rgba(0,0,0,.6),inset 0 0 30px rgba(0,0,0,.35);}
  svg{width:100%;height:auto;display:block;user-select:none;}
  #lines-0,#lines-1,#lines-2,#text-0,#text-1,#text-2{transition:transform .5s cubic-bezier(0.34,1.56,0.64,1);}
  .num{font-family:'Cormorant Garamond',serif;font-weight:500;text-anchor:middle;dominant-baseline:middle;
    pointer-events:none;cursor:pointer;fill:#1a1206;transition:fill .3s,font-weight .2s;}
  .num.highlighted{fill:var(--ink);font-weight:800;}
  .num.draw-hit{fill:#1a6b3c;font-weight:800;}
  .highlight-dot{fill:var(--gold-fill);opacity:0;pointer-events:none;transition:opacity .3s;}
  .draw-dot{fill:#4fae7a;opacity:0;pointer-events:none;transition:opacity .3s;}
  .compass-label{font-family:'Cormorant Garamond',serif;font-weight:700;font-size:26px;fill:var(--ink);text-anchor:middle;dominant-baseline:middle;}
  .ring-track{fill:transparent;cursor:grab;touch-action:none;}
  .ring-track.dragging{cursor:grabbing;}
  .divider{stroke:var(--ink-soft);stroke-width:1;opacity:.35;}
  .band{fill:none;stroke:var(--ink-soft);stroke-width:1.2;opacity:.55;}
  .gap-mark{fill:var(--ink);opacity:.14;}
  .opposite-line{stroke:var(--gold-fill);stroke-width:2;stroke-dasharray:4 4;opacity:.6;}
  .panel{display:flex;flex-direction:column;gap:14px;max-height:88vh;overflow-y:auto;padding-right:4px;}
  .panel::-webkit-scrollbar{width:6px;}
  .panel::-webkit-scrollbar-thumb{background:var(--brass);border-radius:3px;}
  .card{background:var(--panel-bg);border:1px solid #4a3a20;border-radius:12px;padding:13px 15px;}
  .card h3{margin:0 0 10px;font-family:'Cormorant Garamond',serif;font-weight:600;font-size:17px;
    color:var(--brass-hi);display:flex;align-items:center;gap:8px;}
  .swatch{width:11px;height:11px;border-radius:50%;display:inline-block;}
  .btn-row{display:flex;gap:8px;align-items:center;}
  button.step{flex:1;background:#332512;border:1px solid var(--brass);color:var(--paper-1);
    font-family:'Inter',sans-serif;font-size:13px;padding:9px 6px;border-radius:8px;cursor:pointer;transition:all .15s;}
  button.step:hover{background:#40300f;border-color:var(--brass-hi);}
  button.step.primary{background:var(--brass);border-color:var(--brass-hi);color:#1a1206;font-weight:600;}
  button.step.primary:hover{background:var(--brass-hi);}
  button.step.danger{border-color:var(--garnet);color:#e0a0a0;}
  button.step.danger:hover{background:rgba(140,42,42,.2);}
  button.step:disabled{opacity:.4;cursor:not-allowed;}
  table{width:100%;border-collapse:collapse;}
  table th{font-weight:500;font-size:12px;color:#c9b98f;text-align:center;padding-bottom:6px;border-bottom:1px solid #4a3a20;}
  table td{text-align:center;padding:6px 4px;font-family:'Cormorant Garamond',serif;font-weight:600;
    font-size:18px;color:var(--paper-1);border-bottom:1px solid #332512;}
  table td.blank{color:#8a7a5c;opacity:.7;font-style:italic;font-size:13px;}
  .ring-name-cell{text-align:left;font-family:'Inter',sans-serif;font-weight:500;font-size:12px;color:#c9b98f;}
  .hint{font-size:12px;line-height:1.55;color:#b3a37e;padding:10px 4px 0;}
  textarea,input[type=number],input[type=text],select,input[type=file]{background:var(--input-bg);border:1px solid var(--brass);
    color:var(--paper-1);font-family:'Cormorant Garamond',serif;font-size:16px;border-radius:8px;padding:8px;width:100%;}
  select{font-family:'Inter',sans-serif;font-size:12px;}
  input[type=file]{font-family:'Inter',sans-serif;font-size:12px;padding:6px;}
  input[type=file]::file-selector-button{background:var(--brass);border:1px solid var(--brass-hi);color:#1a1206;
    font-weight:600;padding:6px 12px;border-radius:6px;cursor:pointer;font-size:12px;margin-right:8px;}
  .chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px;}
  .chip{background:#332512;border:1px solid var(--gold-fill);color:var(--gold-fill);
    font-family:'Cormorant Garamond',serif;font-weight:600;font-size:14px;padding:2px 9px;border-radius:12px;cursor:pointer;}
  .chip.draw-chip{border-color:#4fae7a;color:#4fae7a;}
  .archive-info{display:flex;align-items:center;gap:8px;padding:6px 10px;background:rgba(0,0,0,.15);
    border-radius:8px;margin-bottom:8px;font-size:12px;color:#c9b98f;}
  .status-dot{width:8px;height:8px;border-radius:50%;display:inline-block;}
  .status-ok{background:#4fae7a;} .status-no{background:#e0a0a0;}
  .progress-bar-bg{height:6px;background:rgba(0,0,0,.3);border-radius:3px;overflow:hidden;margin-top:8px;display:none;}
  .progress-bar-fill{height:100%;background:var(--brass-hi);border-radius:3px;transition:width .3s;width:0%;}
  .progress-label{font-size:11px;color:#b3a37e;margin-top:4px;text-align:center;min-height:14px;}
  .archive-log{max-height:110px;overflow-y:auto;margin-top:8px;padding:6px;background:rgba(0,0,0,.2);
    border-radius:6px;font-family:'Courier New',monospace;font-size:10.5px;color:#b3a37e;line-height:1.5;display:none;}
  .log-ok{color:#4fae7a;} .log-warn{color:#e0b04a;} .log-err{color:#e0a0a0;}
  .archive-use{margin-top:10px;padding-top:10px;border-top:1px solid #4a3a20;}
  .validation-report{margin-top:8px;font-size:11.5px;max-height:160px;overflow-y:auto;
    padding:8px;background:rgba(0,0,0,.15);border-radius:8px;display:none;}
  .validation-report table{margin-top:6px;}
  .validation-report th{font-size:10px;padding:3px 4px;}
  .validation-report td{font-size:10.5px;padding:3px 4px;}
  .esiti-card{border:2px solid var(--brass-hi) !important;}
  .esiti-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;}
  .esiti-subtitle{font-size:11px;color:#b3a37e;margin-bottom:10px;line-height:1.4;}
  .esiti-grid{display:flex;flex-wrap:wrap;gap:12px;}
  .esito-item{display:flex;flex-direction:column;align-items:center;gap:4px;}
  .esito-num{position:relative;width:58px;height:58px;border-radius:50%;
    display:flex;flex-direction:column;align-items:center;justify-content:center;
    background:radial-gradient(circle at 30% 30%, #f7d774, var(--gold-fill));
    color:#1a1206;font-weight:700;cursor:pointer;
    box-shadow:0 3px 10px rgba(0,0,0,.4);transition:transform .15s;}
  .esito-num:hover{transform:scale(1.1);}
  .esito-num.top1{background:radial-gradient(circle at 30% 30%, #ff9d6c, #e0552c);color:#fff;
    box-shadow:0 0 14px rgba(224,85,44,.7);}
  .esito-num .n{font-size:21px;font-family:'Cormorant Garamond',serif;line-height:1;}
  .esito-num .s{font-size:9px;font-weight:600;opacity:.85;}
  .esito-fonti{display:flex;gap:3px;flex-wrap:wrap;justify-content:center;max-width:70px;}
  .fonte-dot{width:8px;height:8px;border-radius:50%;}
  .fd-motore{background:#e0b04a;} .fd-conv{background:#7fd4d4;} .fd-backtest{background:#4fae7a;}
  .esiti-legenda{display:flex;gap:14px;margin-top:12px;font-size:10px;color:#b3a37e;flex-wrap:wrap;}
  .esiti-legenda span{display:flex;align-items:center;gap:5px;}
  .esiti-vuoto{text-align:center;color:#8a7a5c;font-size:12px;padding:18px 8px;line-height:1.6;}
  .esiti-refresh{flex:0 0 auto !important;padding:7px 14px !important;}
  .conv-archive-box{background:rgba(15,110,110,.12);border:1px solid var(--accent);border-radius:8px;padding:10px;margin-bottom:10px;}
  .conv-archive-box .title{font-size:12px;font-weight:600;color:#7fd4d4;margin-bottom:6px;}
  .pred-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px;}
  .pred-box{background:rgba(0,0,0,.18);border-radius:8px;padding:8px;text-align:center;}
  .pred-box .lbl{font-size:10.5px;letter-spacing:.08em;color:#c9b98f;text-transform:uppercase;}
  .pred-box .val{font-family:'Cormorant Garamond',serif;font-size:20px;font-weight:700;color:var(--gold-fill);}
  .cand-list{margin-top:10px;max-height:180px;overflow-y:auto;}
  .cand-item{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;background:rgba(0,0,0,.15);
    margin-bottom:4px;font-size:12.5px;cursor:pointer;transition:background .15s;}
  .cand-item:hover{background:rgba(224,176,74,.18);}
  .cand-item .cn{font-family:'Cormorant Garamond',serif;font-size:17px;font-weight:700;color:var(--gold-fill);min-width:28px;text-align:center;}
  .cm{color:#b3a37e;flex:1;line-height:1.35;}
  .cs{color:#4fae7a;font-weight:600;}
  .fig-badge{display:inline-block;padding:2px 9px;border-radius:10px;font-size:11px;margin:2px 3px 2px 0;border:1px solid;}
  .distance-list{max-height:160px;overflow-y:auto;padding:4px;}
  .distance-item{display:grid;grid-template-columns:1fr auto auto;gap:8px;padding:5px 8px;
    background:rgba(0,0,0,.15);border-radius:6px;margin-bottom:4px;font-family:'Cormorant Garamond',serif;font-size:14px;}
  .figure-item{display:flex;justify-content:space-between;padding:5px 8px;background:rgba(140,42,42,.15);
    border-radius:6px;margin-bottom:4px;font-size:13px;}
  .figure-item .type{color:var(--gold-fill);font-weight:600;}
  .alignment-result{padding:8px;background:rgba(15,110,110,.15);border-radius:6px;margin-top:8px;font-size:13px;
    text-align:center;min-height:38px;display:flex;align-items:center;justify-content:center;}
  .config-input-row{display:flex;gap:8px;margin-bottom:8px;}
  .config-input-row input{flex:1;}
  .config-input-row button{background:var(--brass);border:1px solid var(--brass-hi);color:#1a1206;
    font-weight:600;padding:6px 12px;border-radius:6px;cursor:pointer;font-size:13px;}
  .saved-list{max-height:140px;overflow-y:auto;}
  .saved-item{display:flex;justify-content:space-between;align-items:center;padding:6px 8px;
    background:rgba(0,0,0,.15);border-radius:6px;margin-bottom:4px;font-size:13px;}
  .saved-item .name{color:var(--brass-hi);font-weight:500;cursor:pointer;flex:1;}
  .saved-item .del{color:#e0a0a0;cursor:pointer;padding:2px 6px;}
  .hist-list{margin-top:8px;max-height:120px;overflow-y:auto;}
  .hist-item{display:flex;align-items:center;gap:6px;padding:5px 8px;background:rgba(0,0,0,.15);
    border-radius:6px;margin-bottom:4px;font-size:12px;}
  .hist-item .ruota{color:var(--brass-hi);font-weight:600;min-width:60px;}
  .hist-item .nums{font-family:'Cormorant Garamond',serif;font-size:14px;flex:1;}
  .hist-item .del{color:#e0a0a0;cursor:pointer;}
  .conv-item{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;
    background:rgba(15,110,110,.14);margin-bottom:4px;font-size:12.5px;cursor:pointer;}
  .conv-item:hover{background:rgba(15,110,110,.3);}
  .conv-item .cn{font-family:'Cormorant Garamond',serif;font-size:17px;font-weight:700;color:#7fd4d4;min-width:28px;text-align:center;}
  .conv-badge{background:var(--accent);color:#f4ecd8;border-radius:10px;padding:1px 8px;font-size:11px;font-weight:600;}
  .fig-shape{fill-opacity:.10;stroke-width:2.5;stroke-linejoin:round;stroke-linecap:round;}
  .fig-shape-glow{filter:drop-shadow(0 0 5px currentColor);}
  .fig-ray{stroke-width:1.4;stroke-dasharray:7 5;opacity:.45;fill:none;}
  .fig-vertex{stroke-width:2.5;fill-opacity:.95;}
  .fig-vertex-ring{fill:none;stroke-width:1.5;opacity:.6;}
  .fig-center{opacity:.9;}
  .fig-arc{fill:none;stroke-width:2.2;opacity:.55;stroke-linecap:round;}
  .fig-arc-inner{fill:none;stroke-width:1.5;opacity:.35;stroke-dasharray:4 3;}
  .fig-diameter{stroke-width:2.5;opacity:.7;stroke-linecap:round;}
  .fig-label-bg{fill:rgba(0,0,0,.55);rx:4;ry:4;}
  .fig-label{font:700 11px Inter,sans-serif;fill:#fff;text-anchor:middle;dominant-baseline:middle;
    paint-order:stroke;stroke:rgba(0,0,0,.6);stroke-width:3px;}
  .fig-angle-label{font:600 9px Inter,sans-serif;fill-opacity:.85;text-anchor:middle;}
  .fig-crosshair{stroke-width:1.5;opacity:.7;}
  @keyframes figPulse{0%,100%{stroke-opacity:.95}50%{stroke-opacity:.45}}
  .fig-pulse{animation:figPulse 2.6s ease-in-out infinite;}
  .bt-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px;}
  .bt-grid label{font-size:11px;color:#c9b98f;display:block;margin-bottom:3px;}
  .bt-result{margin-top:10px;padding:12px;background:rgba(0,0,0,.18);border-radius:8px;font-size:12px;}
  .bt-row{display:flex;align-items:center;gap:6px;margin:6px 0;flex-wrap:wrap;}
  .bt-num{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;
    border-radius:50%;font-weight:700;font-family:'Cormorant Garamond',serif;font-size:16px;
    background:#332512;border:1px solid var(--gold-fill);color:var(--gold-fill);transition:transform .15s;}
  .bt-num:hover{transform:scale(1.08);}
  .bt-num.actual{background:#1a6b3c;border-color:#4fae7a;color:#fff;}
  .bt-num.hit{background:var(--gold-fill);color:#1a1206;border:2px solid #4fae7a;box-shadow:0 0 8px rgba(79,174,122,.6);}
  .bt-label{font-size:11px;color:#c9b98f;margin:8px 0 4px;}
  input[type=date]{background:var(--input-bg);border:1px solid var(--brass);color:var(--paper-1);
    border-radius:8px;padding:8px;width:100%;font-family:'Inter',sans-serif;font-size:13px;}
  input[type=date]::-webkit-calendar-picker-indicator{filter:invert(.7);cursor:pointer;}
  .bt-jump{cursor:pointer;text-decoration:underline;color:var(--brass-hi);}
  .notification{position:fixed;top:20px;right:20px;background:var(--brass);color:#1a1206;padding:12px 20px;
    border-radius:8px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.3);opacity:0;transform:translateY(-20px);
    transition:all .3s;z-index:1000;pointer-events:none;}
  .notification.show{opacity:1;transform:translateY(0);}
  .guide-modal{position:fixed;inset:0;background:rgba(0,0,0,.7);display:none;align-items:center;justify-content:center;z-index:2000;padding:16px;}
  .guide-modal.open{display:flex;}
  .guide-box{background:var(--panel-bg);border:1px solid var(--brass);border-radius:14px;max-width:760px;width:100%;
    max-height:85vh;overflow-y:auto;padding:24px 28px;}
  .guide-box h2{font-family:'Cormorant Garamond',serif;color:var(--brass-hi);margin:0 0 12px;}
  .guide-box h4{color:var(--brass-hi);margin:16px 0 6px;font-family:'Cormorant Garamond',serif;font-size:17px;}
  .guide-box p,.guide-box li{font-size:13.5px;line-height:1.6;}
  .guide-close{float:right;background:none;border:1px solid var(--garnet);color:#e0a0a0;border-radius:6px;padding:4px 10px;cursor:pointer;}
  .stats-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:6px;margin-top:8px;}
  .stat-item{text-align:center;padding:8px;background:rgba(0,0,0,.15);border-radius:6px;cursor:pointer;transition:background .15s;}
  .stat-item:hover{background:rgba(224,176,74,.18);}
  .stat-item .stat-num{font-size:18px;font-weight:700;color:var(--gold-fill);font-family:'Cormorant Garamond',serif;}
  .stat-item .stat-freq{font-size:10px;color:#c9b98f;margin-top:2px;}
  .coerenza-box{margin-top:10px;padding:10px;background:rgba(0,0,0,.18);border-radius:8px;font-size:12px;}
  .coerenza-item{padding:4px 6px;margin:3px 0;background:rgba(79,174,122,.12);border-radius:4px;color:#4fae7a;}
  .pattern-box{margin-top:8px;font-size:11.5px;}
  .pattern-item{display:flex;justify-content:space-between;padding:5px 8px;background:rgba(0,0,0,.15);
    border-radius:6px;margin-bottom:4px;}
  .pattern-item .pair{color:var(--gold-fill);font-weight:600;font-family:'Cormorant Garamond',serif;font-size:14px;}
  .pattern-item .freq{color:#c9b98f;}
  .download-info{background:rgba(224,176,74,.15);border:1px solid var(--brass);border-radius:8px;padding:10px;margin-bottom:10px;font-size:11.5px;line-height:1.5;}
  .download-info strong{color:var(--brass-hi);}
  .download-info ol{margin:6px 0;padding-left:20px;}
  .download-info li{margin:3px 0;}
  .download-info a{color:var(--brass-hi);text-decoration:underline;}
</style>
</head>
<body>
<div class="wrap">
  <h1>Cerchio di Spilimbergo — Suite Completa v2.2</h1>
  <p class="subtitle">Archivio ZIP Brightstar · Cache IndexedDB · Ciclometria Avanzata · Motore Previsivo Potenziato</p>

  <div class="toolbar">
    <button id="themeToggle">🌙 Tema</button>
    <button id="shareBtn">🔗 Condividi</button>
    <button id="importDemoBtn">📥 Demo</button>
    <button id="clearAllBtn">🗑️ Pulisci Tutto</button>
    <button id="guideBtn">❓ Guida</button>
  </div>

  <div class="layout">
    <div class="wheel-card"><svg id="wheel" viewBox="0 0 700 700"></svg></div>

    <div class="panel">
      <!-- Ghiere -->
      <div class="card" data-ring="0"><h3><span class="swatch" style="background:#8c2a2a"></span>Anello interno</h3>
        <div class="btn-row"><button class="step" data-ring="0" data-dir="-1">↺</button><button class="step" data-ring="0" data-dir="1">↻</button></div></div>
      <div class="card" data-ring="1"><h3><span class="swatch" style="background:#8c2a2a"></span>Anello medio</h3>
        <div class="btn-row"><button class="step" data-ring="1" data-dir="-1">↺</button><button class="step" data-ring="1" data-dir="1">↻</button></div></div>
      <div class="card" data-ring="2"><h3><span class="swatch" style="background:#8c2a2a"></span>Anello esterno</h3>
        <div class="btn-row"><button class="step" data-ring="2" data-dir="-1">↺</button><button class="step" data-ring="2" data-dir="1">↻</button></div></div>

      <!-- Lettura cardinale -->
      <div class="card">
        <h3>Lettura ai 4 punti cardinali</h3>
        <table><tr><th></th><th>N</th><th>S</th><th>E</th><th>O</th></tr>
          <tr><td class="ring-name-cell">Interno</td><td id="r0-N"></td><td id="r0-S"></td><td id="r0-E"></td><td id="r0-O"></td></tr>
          <tr><td class="ring-name-cell">Medio</td><td id="r1-N"></td><td id="r1-S"></td><td id="r1-E"></td><td id="r1-O"></td></tr>
          <tr><td class="ring-name-cell">Esterno</td><td id="r2-N"></td><td id="r2-S"></td><td id="r2-E"></td><td id="r2-O"></td></tr>
        </table>
        <button class="step danger" id="resetBtn" style="margin-top:8px;width:100%;">Riporta le ghiere a riposo</button>
      </div>

      <!-- STATISTICHE RAPIDE -->
      <div class="card">
        <h3>📊 Statistiche & Pattern</h3>
        <div class="btn-row">
          <select id="statsRuota" style="flex:1;"></select>
          <button class="step" id="statsRun" style="flex:0 0 auto;padding:8px 14px;">Analizza</button>
        </div>
        <div class="btn-row" style="margin-top:6px;">
          <button class="step" id="statsTop10">Top 10 Frequenti</button>
          <button class="step" id="statsPattern">Pattern Coppie</button>
        </div>
        <div id="statsResults"></div>
      </div>

      <!-- ARCHIVI ESTRAZIONI -->
      <div class="card" id="archiveCard">
        <h3>📚 Archivio Estrazioni</h3>
        
        <div class="download-info">
          <strong>📥 Come caricare l'archivio:</strong>
          <ol>
            <li>Scarica manualmente il file: <a href="https://www.brightstarlottery.it/STORICO_ESTRAZIONI_LOTTO/storico.zip" target="_blank">storico.zip</a></li>
            <li>Clicca su <strong>"📂 Carica ZIP Locale"</strong> qui sotto</li>
            <li>Seleziona il file ZIP appena scaricato</li>
            <li>Il sistema lo decomprimerà e creerà la cache automaticamente</li>
          </ol>
          <em>💡 La cache resta salvata nel browser per usi futuri!</em>
        </div>
        
        <div class="archive-info"><span class="status-dot status-no" id="cacheStatusDot"></span><span id="cacheInfoText">Nessuna cache</span></div>
        
        <div class="btn-row">
          <button class="step primary" id="btnLoadLocal">📂 Carica ZIP Locale</button>
          <input type="file" id="fileInput" accept=".zip" style="display:none;">
        </div>
        
        <div class="btn-row" style="margin-top:6px;">
          <button class="step" id="btnLoadCache" disabled>💾 Carica Cache</button>
          <button class="step" id="btnValida" disabled>🔍 Valida</button>
          <button class="step danger" id="btnClearCache">🗑️ Cancella</button>
        </div>
        
        <div class="progress-bar-bg" id="progressWrap"><div class="progress-bar-fill" id="progressBar"></div></div>
        <div class="progress-label" id="progressLabel"></div>
        <div class="archive-log" id="archiveLog"></div>
        <div class="validation-report" id="validationReport"></div>
        
        <div class="archive-use">
          <label style="font-size:11px;color:#c9b98f;display:block;margin-bottom:3px;">Epoca storica</label>
          <select id="epocaSelect" style="margin-bottom:6px;">
            <option value="completa">Archivio Completo</option>
            <option value="venus">Venus (2005-oggi)</option>
            <option value="quattro">4 Estrazioni (2023-oggi)</option>
          </select>
          <label style="font-size:11px;color:#c9b98f;display:block;margin-bottom:3px;">Usa archivio sul Cerchio</label>
          <div class="btn-row"><select id="archivioRuotaSelect" style="flex:1;"></select></div>
          <div class="btn-row" style="margin-top:6px;">
            <button class="step" id="btnUseLast1" disabled>🎯 Ultima estrazione</button>
            <button class="step" id="btnUseLast3" disabled>🎯 Ultime 3</button>
          </div>
        </div>
      </div>

      <!-- NUMERI PROMETTENTI (ESITI AGGREGATI) -->
      <div class="card esiti-card">
        <div class="esiti-header">
          <h3 style="margin:0;">🎯 Numeri Promettenti</h3>
          <button class="step primary esiti-refresh" id="esitiRefresh">↻ Aggiorna</button>
        </div>
        <div class="esiti-subtitle">
          Aggrega gli esiti di <b>Motore Previsivo</b>, <b>Convergenze</b> e <b>Verifica a Ritroso</b> con scoring avanzato.
          Arancio = top assoluto. Clicca un pallino per evidenziarlo sul Cerchio.
        </div>
        <div id="esitiContent">
          <div class="esiti-vuoto">
            Esegui almeno un'analisi (⚡ Motore, 🔀 Convergenze o 🕰️ Verifica a Ritroso)<br>
            poi premi <b>↻ Aggiorna</b> per vedere i numeri promettenti.
          </div>
        </div>
      </div>

      <!-- VERIFICA A RITROSO -->
      <div class="card" id="backtestCard">
        <h3>🕰️ Verifica a Ritroso</h3>
        <div class="bt-grid">
          <div>
            <label>Ruota</label>
            <select id="btRuotaSelect"></select>
          </div>
          <div>
            <label>Data (calendario)</label>
            <input type="date" id="btDateInput">
          </div>
          <div>
            <label>Estrazioni base</label>
            <input type="number" id="btNumBase" value="60" min="10" max="500">
          </div>
          <div>
            <label>Top candidati</label>
            <input type="number" id="btTopN" value="10" min="5" max="45">
          </div>
        </div>
        <div class="btn-row" style="margin-top:8px;">
          <button class="step primary" id="btRun">🔍 Verifica a ritroso</button>
        </div>
        <div id="btResults"></div>
      </div>

      <!-- MOTORE PREVISIVO L1 -->
      <div class="card">
        <h3>🤖 Motore Previsivo <span style="font-size:11px;color:#8a7a5c;">L1 Potenziato</span></h3>
        <div class="btn-row"><button class="step primary" id="engineRun">⚡ Genera figure e previsione</button></div>
        <div class="btn-row" style="margin-top:6px;">
          <button class="step" id="heatToggle">🔥 Calore</button>
          <button class="step" id="tourBtn">🎬 Tour</button>
          <button class="step" id="engineClear">🧹</button>
        </div>
        <div id="engineResults"></div>
        <div style="font-size:10.5px;color:#8a7a5c;font-style:italic;margin-top:8px;text-align:center;">Metodo tradizionale: solo studio e intrattenimento. Gioca responsabilmente.</div>
      </div>

      <!-- STORICO L2 -->
      <div class="card">
        <h3>📜 Storico & Convergenze <span style="font-size:11px;color:#8a7a5c;">L2</span></h3>

        <!-- CARICA RUOTE DA ARCHIVIO -->
        <div class="conv-archive-box">
          <div class="title">📥 Carica ruote dall'archivio</div>
          <div class="btn-row">
            <select id="convArchivioRuota" style="flex:1;"></select>
            <input type="number" id="convArchivioN" value="10" min="2" max="100" style="width:70px;">
          </div>
          <button class="step" id="btnCaricaRuoteConv" style="margin-top:6px;width:100%;">📥 Carica estrazioni nelle Convergenze</button>
        </div>

        <textarea id="historyInput" rows="3" placeholder="Oppure inserisci a mano, una estrazione per riga:
Bari 7 23 45 68 81
Napoli 12 34 56 72 90"></textarea>
        <div class="btn-row" style="margin-top:6px;">
          <button class="step" id="histLoad">📥 Carica</button>
          <button class="step" id="histDemo">Demo</button>
          <button class="step" id="histClear">🧹</button>
        </div>
        <div class="hist-list" id="histList"></div>
        <div class="btn-row" style="margin-top:6px;">
          <select id="ruotaFilter"></select>
          <button class="step primary" id="convRun">🔀 Convergenze</button>
        </div>
        <div id="convResults"></div>
      </div>

      <!-- Estrazione singola -->
      <div class="card">
        <h3>Estrazione da analizzare</h3>
        <textarea id="drawInput" rows="2" placeholder="Incolla i numeri: 4 26 38 60 61"></textarea>
        <div class="btn-row" style="margin-top:6px;">
          <button class="step" id="drawGo">Evidenzia</button>
          <button class="step" id="drawClear">Pulisci</button>
        </div>
        <div class="chips" id="drawChips"></div>
        <div id="coerenzaBox"></div>
      </div>

      <!-- Distanze e figure -->
      <div class="card">
        <h3>📐 Distanze e Figure</h3>
        <div class="distance-list" id="distanceList"><div style="text-align:center;color:#8a7a5c;font-size:12px;padding:8px;">Evidenzia almeno 2 numeri</div></div>
        <div id="figureList"></div>
      </div>

      <!-- Allineamenti potenziati -->
      <div class="card">
        <h3>🎯 Cerca Allineamenti <span style="font-size:11px;color:#8a7a5c;">potenziato</span></h3>
        <div class="btn-row" style="margin-bottom:6px;">
          <select id="alignMode" style="flex:1;">
            <option value="entrambi">🟡 Oro + 🟢 Verde</option>
            <option value="oro">🟡 Solo numeri evidenziati</option>
            <option value="verde">🟢 Solo estrazione caricata</option>
          </select>
        </div>
        <button class="step primary" id="findAlignmentBtn" style="width:100%;">🔍 Trova migliori configurazioni</button>
        <div class="alignment-result" id="alignmentResult"><span style="color:#8a7a5c;font-size:12px;">Evidenzia numeri o carica una estrazione, poi cerca</span></div>
        <div id="alignTopList"></div>
        <div id="alignDetail"></div>
      </div>

      <!-- Configurazioni -->
      <div class="card">
        <h3>💾 Configurazioni</h3>
        <div class="config-input-row"><input type="text" id="configNameInput" placeholder="Nome configurazione"><button id="saveConfigBtn">Salva</button></div>
        <div class="saved-list" id="savedList"></div>
      </div>

      <!-- Scova numero -->
      <div class="card">
        <h3>Scova numero</h3>
        <div class="btn-row"><input type="number" id="finderInput" min="1" max="90" placeholder="1-90"><button class="step" id="finderGo" style="flex:0 0 auto;padding:8px 14px;">Trova</button></div>
        <div class="chips" id="chips"></div>
        <button class="step danger" id="clearHighlights" style="width:100%;margin-top:8px;">Pulisci evidenziazioni</button>
      </div>

      <p class="hint">Trascina un anello per ruotarlo. 📚 Carica l'archivio ZIP locale (cache su IndexedDB). 🕰️ Verifica a Ritroso con calendario. 🎯 Numeri Promettenti aggrega tutti gli esiti con scoring avanzato. 📊 Statistiche frequenze e pattern.</p>
    </div>
  </div>
</div>

<!-- Modale guida -->
<div class="guide-modal" id="guideModal">
  <div class="guide-box">
    <button class="guide-close" id="guideClose">✕ Chiudi</button>
    <h2>Guida alla Suite Completa v2.2</h2>
    <h4>1. Archivio Estrazioni (ZIP Brightstar + Cache IndexedDB)</h4>
    <p>📂 <strong>Carica ZIP Locale</strong>: Scarica manualmente il file <code>storico.zip</code> da Brightstar Lottery, poi caricalo nel browser. Il sistema lo decomprime automaticamente e salva su <b>IndexedDB</b>. La cache resta salvata per usi futuri.</p>
    <h4>2. Statistiche & Pattern</h4>
    <p>Analizza frequenze dei numeri e pattern di coppie per ogni ruota. Top 10 numeri più frequenti e coppie più uscite.</p>
    <h4>3. Numeri Promettenti (Scoring Avanzato)</h4>
    <p>Aggrega automaticamente i risultati di <b>Motore Previsivo</b>, <b>Convergenze</b> e <b>Verifica a Ritroso</b> con sistema di pesatura intelligente che premia la coerenza tra fonti.</p>
    <h4>4. Verifica a Ritroso (Backtest)</h4>
    <p>Scegli ruota + data dal calendario. Il sistema ricostruisce le estrazioni fino a quella data, genera i numeri "dovuti" e li confronta con l'estrazione reale successiva.</p>
    <h4>5. Analisi Coerenza Geometrica</h4>
    <p>Quando carichi una estrazione, il sistema valuta automaticamente la coerenza geometrica (figure ciclometriche presenti).</p>
    <h4>6. Storico & Convergenze (con archivio)</h4>
    <p>Nel box "Carica ruote dall'archivio" puoi scegliere una ruota o tutte, impostare N estrazioni recenti, e caricarle automaticamente nelle Convergenze.</p>
    <h4>7. Cerchio di Spilimbergo</h4>
    <p>Tre ghiere da 30 numeri (passo 23). Trascina o usa ↺/↻ per ruotare. La tabella mostra i numeri sotto N/S/E/O.</p>
    <h4>⚠️ Nota importante</h4>
    <p>Metodi della tradizione lottologica, privi di validità scientifica. Il Lotto è casuale. Gioca responsabilmente.</p>
  </div>
</div>
<div class="notification" id="notification"></div>

<script>
// ============================================================
// PARAMETRI E COSTANTI - AGGIORNATI CON NUOVI CODICI RUOTA
// ============================================================
const RUOTE_NOME = {
  'BA':'Bari','CA':'Cagliari','FI':'Firenze','GE':'Genova','MI':'Milano',
  'NA':'Napoli','PA':'Palermo','RM':'Roma','RN':'Nazionale','TO':'Torino','VE':'Venezia'
};

// Mappatura codici dal file storico.txt
const CODICE_TO_NOME = {
  'BA':'Bari','CA':'Cagliari','FI':'Firenze','GE':'Genova','MI':'Milano',
  'NA':'Napoli','PA':'Palermo','RM':'Roma','RN':'Nazionale','TO':'Torino','VE':'Venezia'
};

const EPOCHE = {
  'venus':    { nome:'Venus (2005-oggi)', dataInizio:'2005-05-05' },
  'quattro':  { nome:'4 Estrazioni (2023-oggi)', dataInizio:'2023-07-07' },
  'completa': { nome:'Archivio Completo', dataInizio:null }
};

// ============================================================
// STATO GLOBALE
// ============================================================
let archivio = null;
let LAST_BACKTEST = [];
let STATISTICHE_CACHE = {};

// ============================================================
// ARCHIVIO: CACHE SU INDEXEDDB
// ============================================================
const DB_NAME = 'icq_archivio_db';
const DB_STORE = 'estrazioni';
const CACHE_META_KEY = 'archivio_estrazioni_meta';

function apriDB(){
  return new Promise((resolve, reject)=>{
    const req = indexedDB.open(DB_NAME, 1);
    req.onupgradeneeded = e => {
      const db = e.target.result;
      if(!db.objectStoreNames.contains(DB_STORE)) db.createObjectStore(DB_STORE, { keyPath:'id' });
    };
    req.onsuccess = e => resolve(e.target.result);
    req.onerror = () => reject(req.error);
  });
}
function cacheEsiste(){ return localStorage.getItem(CACHE_META_KEY) !== null; }
function cacheUltimaData(){
  const m = localStorage.getItem(CACHE_META_KEY);
  return m ? JSON.parse(m).ultimaData : null;
}
async function salvaCache(righe, ultimaData){
  try{
    const db = await apriDB();
    const compatte = righe.map(r => [r.data, r.ruota, r.n1, r.n2, r.n3, r.n4, r.n5]);
    await new Promise((resolve, reject)=>{
      const tx = db.transaction(DB_STORE, 'readwrite');
      tx.objectStore(DB_STORE).put({ id:'archivio', righe:compatte, ultimaData, salvatoIl:new Date().toISOString() });
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error);
    });
    db.close();
    localStorage.setItem(CACHE_META_KEY, JSON.stringify({ultimaData, nRighe:righe.length}));
    if(navigator.storage && navigator.storage.persist) navigator.storage.persist();
    logArchivio(`✅ Cache salvata (IndexedDB): ${righe.length.toLocaleString('it-IT')} righe, ultima: ${ultimaData}`, 'ok');
    return true;
  }catch(e){
    logArchivio(`❌ Errore salvataggio IndexedDB: ${e.message}`, 'err');
    return false;
  }
}
async function caricaCache(){
  try{
    const db = await apriDB();
    const dati = await new Promise((resolve, reject)=>{
      const tx = db.transaction(DB_STORE, 'readonly');
      const req = tx.objectStore(DB_STORE).get('archivio');
      req.onsuccess = () => resolve(req.result);
      req.onerror = () => reject(req.error);
    });
    db.close();
    if(!dati) return null;
    const righe = dati.righe.map(a => ({data:a[0], ruota:a[1], n1:a[2], n2:a[3], n3:a[4], n4:a[5], n5:a[6]}));
    archivio = { righe, ultimaData: dati.ultimaData };
    logArchivio(`📂 Cache caricata (IndexedDB): ${righe.length.toLocaleString('it-IT')} righe, ultima: ${dati.ultimaData}`, 'ok');
    return { righe, ultimaData: dati.ultimaData };
  }catch(e){
    logArchivio(`❌ Errore lettura cache: ${e.message}`, 'err');
    return null;
  }
}
async function cancellaCache(){
  try{
    const db = await apriDB();
    await new Promise(resolve=>{
      const tx = db.transaction(DB_STORE, 'readwrite');
      tx.objectStore(DB_STORE).delete('archivio');
      tx.oncomplete = () => resolve();
      tx.onerror = () => resolve();
    });
    db.close();
  }catch(e){}
  localStorage.removeItem(CACHE_META_KEY);
  archivio = null;
  document.getElementById('validationReport').style.display = 'none';
  updateArchiveUI();
  logArchivio('🗑️ Cache cancellata', 'warn');
  showNotification('🗑️ Cache cancellata');
}

// ============================================================
// ARCHIVIO: UI, LOG, PROGRESS
// ============================================================
function logArchivio(msg, tipo=''){
  const log = document.getElementById('archiveLog');
  log.style.display = 'block';
  const cls = tipo==='ok'?'log-ok':tipo==='warn'?'log-warn':tipo==='err'?'log-err':'';
  log.innerHTML += `<div class="${cls}">[${new Date().toLocaleTimeString('it-IT')}] ${msg}</div>`;
  log.scrollTop = log.scrollHeight;
}
function setProgress(pct, label){
  document.getElementById('progressWrap').style.display = pct>0 ? 'block' : 'none';
  document.getElementById('progressBar').style.width = pct+'%';
  document.getElementById('progressLabel').textContent = label||'';
}
function updateArchiveUI(){
  const esiste = cacheEsiste() && archivio;
  const dot = document.getElementById('cacheStatusDot');
  const info = document.getElementById('cacheInfoText');
  if(esiste){
    dot.className = 'status-dot status-ok';
    info.textContent = `${archivio.righe.length.toLocaleString('it-IT')} righe · ultima: ${archivio.ultimaData}`;
    document.getElementById('btnValida').disabled = false;
    document.getElementById('btnUseLast1').disabled = false;
    document.getElementById('btnUseLast3').disabled = false;
  } else {
    dot.className = 'status-dot status-no';
    info.textContent = cacheEsiste() ? 'Cache presente (premi Carica Cache)' : 'Nessuna cache';
    document.getElementById('btnLoadCache').disabled = !cacheEsiste();
    document.getElementById('btnValida').disabled = !cacheEsiste();
    document.getElementById('btnUseLast1').disabled = true;
    document.getElementById('btnUseLast3').disabled = true;
  }
}
function popolaSelectRuote(){
  const options = Object.entries(RUOTE_NOME).map(([k,v])=>`<option value="${k}">${k} - ${v}</option>`).join('');
  document.getElementById('archivioRuotaSelect').innerHTML = options;
  document.getElementById('statsRuota').innerHTML = options;
}

// ============================================================
// PARSING DATA AGGIORNATO PER FORMATO YYYY/MM/DD
// ============================================================
function parseData(s){
  if(!s) return null;
  s = String(s).trim();
  // Formato YYYY/MM/DD o YYYY-MM-DD
  let m = s.match(/^(\d{4})[-\/](\d{1,2})[-\/](\d{1,2})/);
  if(m){ const d = new Date(+m[1], +m[2]-1, +m[3]); return isNaN(d.getTime()) ? null : d; }
  // Formato DD/MM/YYYY o DD-MM-YYYY
  m = s.match(/^(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})/);
  if(m){ const d = new Date(+m[3], +m[2]-1, +m[1]); return isNaN(d.getTime()) ? null : d; }
  const d = new Date(s);
  return isNaN(d.getTime()) ? null : d;
}
function toISOData(d){
  return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
}

// ============================================================
// VALIDAZIONE ESTRAZIONE AVANZATA
// ============================================================
function validaEstrazione(riga, ruoteViste, dateViste){
  const errors = [];
 
  // Verifica numeri validi
  const nums = [riga.n1, riga.n2, riga.n3, riga.n4, riga.n5];
  if(nums.some(n => isNaN(n) || n < 1 || n > 90)){
    errors.push('Numeri fuori range 1-90');
  }
 
  // Verifica duplicati nella stessa estrazione
  if(new Set(nums).size !== nums.length){
    errors.push('Numeri duplicati nella stessa estrazione');
  }
 
  // Verifica duplicati tra estrazioni
  const key = `${riga.data}_${riga.ruota}`;
  if(dateViste.has(key)){
    errors.push('Estrazione duplicata');
  } else {
    dateViste.add(key);
  }
 
  // Verifica ruota valida
  if(!CODICE_TO_NOME[riga.ruota]){
    errors.push('Codice ruota non valido');
  }
 
  return { valid: errors.length === 0, errors };
}

// ============================================================
// CARICAMENTO ARCHIVIO ZIP LOCALE - NUOVA FUNZIONE PRINCIPALE
// ============================================================
async function caricaArchivioLocale(file){
  setProgress(10, 'Decomprimo ZIP...');
  logArchivio(`📦 Decomprimo ${file.name}...`);
 
  try{
    const zip = await JSZip.loadAsync(file);
    const storicoFile = zip.file('storico.txt');
    
    if(!storicoFile){
      throw new Error('File storico.txt non trovato nel ZIP');
    }
    
    const raw = await storicoFile.async('text');
    setProgress(50, 'Analizzo estrazioni...');
    logArchivio(`📄 Analisi file storico.txt...`);
    
    const frames = [];
    const ruoteViste = new Set();
    const dateViste = new Set();
    
    for(const line of raw.split('\n')){
      const trimmed = line.trim();
      if(!trimmed) continue;
      
      const parti = trimmed.split('\t');
      if(parti.length !== 7) continue;
      
      const [dataStr, codiceRuota, ...numsStr] = parti;
      
      // Mappatura codici
      if(!CODICE_TO_NOME[codiceRuota]) continue;
      
      const nums = numsStr.map(Number);
      const d = parseData(dataStr);
      if(!d) continue;
      
      const riga = {
        data: dataStr,
        ruota: codiceRuota,
        n1:nums[0], n2:nums[1], n3:nums[2], n4:nums[3], n5:nums[4]
      };
      
      const validazione = validaEstrazione(riga, ruoteViste, dateViste);
      if(!validazione.valid) continue;
      
      frames.push(riga);
    }
    
    setProgress(80, 'Ordino e salvo...');
    
    if(!frames.length){
      logArchivio('❌ Nessun dato valido trovato', 'err');
      showNotification('❌ Nessun dato valido nel file');
      setProgress(100, 'Completato');
      return;
    }
    
    frames.sort((a,b)=>parseData(a.data)-parseData(b.data)||a.ruota.localeCompare(b.ruota));
    
    archivio = { righe: frames, ultimaData: frames[frames.length-1].data };
    const ok = await salvaCache(frames, archivio.ultimaData);
    
    if(!ok){
      logArchivio('⚠️ Dati disponibili SOLO per la sessione corrente', 'warn');
      showNotification('⚠️ Salvataggio non riuscito: dati solo per questa sessione', 4000);
    }
    
    updateArchiveUI();
    setupBtDateRange();
    showNotification(`📥 ${archivio.righe.length.toLocaleString('it-IT')} righe in archivio`);
    
  }catch(e){
    logArchivio(`❌ Errore: ${e.message}`, 'err');
    showNotification(`❌ Errore: ${e.message}`);
  }
 
  setProgress(100, 'Completato');
}

// ============================================================
// ARCHIVIO: VALIDAZIONE
// ============================================================
function validaDati(){
  if(!archivio||!archivio.righe.length){ showNotification('⚠️ Nessun dato'); return; }
  const box = document.getElementById('validationReport');
  box.style.display = 'block';
  const perRuota = {};
  archivio.righe.forEach(r=>{
    if(!perRuota[r.ruota]) perRuota[r.ruota] = {n:0, min:r.data, max:r.data};
    perRuota[r.ruota].n++;
    if(parseData(r.data) < parseData(perRuota[r.ruota].min)) perRuota[r.ruota].min = r.data;
    if(parseData(r.data) > parseData(perRuota[r.ruota].max)) perRuota[r.ruota].max = r.data;
  });
  let html = `<div style="font-weight:600;color:#4fae7a;">✅ VALIDAZIONE OK</div>`;
  html += `<div style="margin-top:4px;">Righe: <b>${archivio.righe.length.toLocaleString('it-IT')}</b> · Ruote: <b>${Object.keys(perRuota).length}/11</b></div>`;
  const venus = archivio.righe.filter(r=>parseData(r.data) >= parseData('2005-05-05')).length;
  const quattro = archivio.righe.filter(r=>parseData(r.data) >= parseData('2023-07-07')).length;
  html += `<div style="margin-top:4px;color:#c9b98f;">Epoca Venus: ${venus.toLocaleString('it-IT')} righe · 4 Estrazioni: ${quattro.toLocaleString('it-IT')} righe</div>`;
  html += `<table><tr><th>Ruota</th><th>Estrazioni</th><th>Prima</th><th>Ultima</th></tr>`;
  for(const [ruota,s] of Object.entries(perRuota)){
    html += `<tr><td>${ruota} ${CODICE_TO_NOME[ruota]||''}</td><td>${s.n.toLocaleString('it-IT')}</td><td>${s.min}</td><td>${s.max}</td></tr>`;
  }
  html += `</table>`;
  box.innerHTML = html;
  logArchivio(`🔍 Validazione completata`, 'ok');
}

// ============================================================
// ARCHIVIO: USO SUL CERCHIO
// ============================================================
function filtraPerEpoca(righe){
  const epocaKey = document.getElementById('epocaSelect').value;
  const epoca = EPOCHE[epocaKey];
  if(!epoca.dataInizio) return righe;
  const dInizio = parseData(epoca.dataInizio);
  return righe.filter(r => parseData(r.data) >= dInizio);
}
function portaEstrazioni(n){
  if(!archivio) return;
  const ruota = document.getElementById('archivioRuotaSelect').value;
  let sub = archivio.righe.filter(r=>r.ruota===ruota);
  sub = filtraPerEpoca(sub);
  sub.sort((a,b)=>parseData(a.data)-parseData(b.data));
  if(!sub.length){ showNotification('⚠️ Nessuna estrazione per questa ruota/epoca'); return; }
  const ultime = sub.slice(-n);
  drawHighlighted.clear();
  ultime.forEach(r=>[r.n1,r.n2,r.n3,r.n4,r.n5].forEach(x=>drawHighlighted.add(x)));
  renderDrawChips(); renderAll();
  analizzaCoerenzaEstrazioneUI([...drawHighlighted]);
  const dateStr = ultime.map(r=>r.data).join(', ');
  showNotification(`🎯 ${CODICE_TO_NOME[ruota]}: ${ultime.length} estraz. (${dateStr})`, 3500);
  logArchivio(`🎯 Caricate ${ultime.length} estrazioni di ${ruota} sul Cerchio`, 'ok');
}

// ============================================================
// STATISTICHE E PATTERN
// ============================================================
function analizzaFrequenze(ruota, ultimeN=100){
  if(!archivio) return null;
 
  const sub = archivio.righe
    .filter(r => r.ruota === ruota)
    .sort((a,b) => parseData(b.data) - parseData(a.data))
    .slice(0, ultimeN);
 
  if(!sub.length) return null;
 
  const freq = new Array(91).fill(0);
  const ultimeUscite = new Array(91).fill(-1);
 
  sub.forEach((r, idx) => {
    [r.n1, r.n2, r.n3, r.n4, r.n5].forEach(n => {
      freq[n]++;
      if(ultimeUscite[n] === -1) ultimeUscite[n] = idx;
    });
  });
 
  const top10 = freq
    .map((f,i) => ({n:i, f, ritardo: ultimeUscite[i]}))
    .filter(x=>x.n>0)
    .sort((a,b)=>b.f-a.f)
    .slice(0,10);
 
  return { freq, ultimeUscite, top10, totale: sub.length };
}

function analizzaPattern(ruota, ultimeN=200){
  if(!archivio) return null;
 
  const sub = archivio.righe
    .filter(r => r.ruota === ruota)
    .sort((a,b) => parseData(b.data) - parseData(a.data))
    .slice(0, ultimeN);
 
  if(!sub.length) return null;
 
  const coppie = new Map();
  const triple = new Map();
 
  sub.forEach(r => {
    const nums = [r.n1, r.n2, r.n3, r.n4, r.n5].sort((a,b)=>a-b);
    
    for(let i=0; i<nums.length; i++){
      for(let j=i+1; j<nums.length; j++){
        const key = `${nums[i]}-${nums[j]}`;
        coppie.set(key, (coppie.get(key)||0) + 1);
      }
    }
    
    for(let i=0; i<nums.length; i++){
      for(let j=i+1; j<nums.length; j++){
        for(let k=j+1; k<nums.length; k++){
          const key = `${nums[i]}-${nums[j]}-${nums[k]}`;
          triple.set(key, (triple.get(key)||0) + 1);
        }
      }
    }
  });
 
  return {
    coppie: [...coppie.entries()]
      .map(([c,f]) => ({coppia:c, freq:f}))
      .sort((a,b) => b.freq - a.freq)
      .slice(0,15),
    triple: [...triple.entries()]
      .map(([c,f]) => ({tripla:c, freq:f}))
      .sort((a,b) => b.freq - a.freq)
      .slice(0,10),
    totale: sub.length
  };
}

function mostraStatisticheTop10(){
  const ruota = document.getElementById('statsRuota').value;
  const analisi = analizzaFrequenze(ruota, 100);
 
  if(!analisi){
    document.getElementById('statsResults').innerHTML = '<div style="color:#8a7a5c;padding:8px;text-align:center;">Carica prima l\'archivio</div>';
    return;
  }
 
  let html = `<div style="color:var(--brass-hi);font-weight:600;margin:8px 0 6px;font-size:12px;">Top 10 numeri più frequenti (${analisi.totale} estrazioni):</div>`;
  html += `<div class="stats-grid">`;
  analisi.top10.forEach(t => {
    html += `<div class="stat-item" data-n="${t.n}">
      <div class="stat-num">${t.n}</div>
      <div class="stat-freq">${t.f} volte</div>
      <div class="stat-freq" style="font-size:9px;opacity:.7;">${t.ritardo===0?'ultimo':'ritardo '+t.ritardo}</div>
    </div>`;
  });
  html += `</div>`;
 
  const box = document.getElementById('statsResults');
  box.innerHTML = html;
 
  box.querySelectorAll('.stat-item').forEach(el => {
    el.addEventListener('click', () => {
      const n = parseInt(el.dataset.n);
      highlighted.add(n);
      renderChips();
      renderAll();
      showNotification(`📊 ${n} evidenziato sul Cerchio`);
    });
  });
 
  showNotification(`📊 Statistiche ${CODICE_TO_NOME[ruota]} caricate`);
}

function mostraStatistichePattern(){
  const ruota = document.getElementById('statsRuota').value;
  const analisi = analizzaPattern(ruota, 200);
 
  if(!analisi){
    document.getElementById('statsResults').innerHTML = '<div style="color:#8a7a5c;padding:8px;text-align:center;">Carica prima l\'archivio</div>';
    return;
  }
 
  let html = `<div style="color:var(--brass-hi);font-weight:600;margin:8px 0 6px;font-size:12px;">Top 15 coppie più frequenti (${analisi.totale} estrazioni):</div>`;
  html += `<div class="pattern-box">`;
  analisi.coppie.slice(0,15).forEach(p => {
    html += `<div class="pattern-item" data-pair="${p.coppia}">
      <span class="pair">${p.coppia}</span>
      <span class="freq">${p.freq} volte</span>
    </div>`;
  });
  html += `</div>`;
 
  html += `<div style="color:var(--brass-hi);font-weight:600;margin:12px 0 6px;font-size:12px;">Top 10 triple più frequenti:</div>`;
  html += `<div class="pattern-box">`;
  analisi.triple.slice(0,10).forEach(p => {
    html += `<div class="pattern-item" data-triple="${p.tripla}">
      <span class="pair">${p.tripla}</span>
      <span class="freq">${p.freq} volte</span>
    </div>`;
  });
  html += `</div>`;
 
  const box = document.getElementById('statsResults');
  box.innerHTML = html;
 
  box.querySelectorAll('.pattern-item').forEach(el => {
    el.addEventListener('click', () => {
      const nums = (el.dataset.pair || el.dataset.triple).split('-').map(Number);
      nums.forEach(n => highlighted.add(n));
      renderChips();
      renderAll();
      showNotification(`📊 ${nums.length} numeri evidenziati`);
    });
  });
 
  showNotification(`📊 Pattern ${CODICE_TO_NOME[ruota]} caricati`);
}

// ============================================================
// ANALISI COERENZA GEOMETRICA
// ============================================================
function analizzaCoerenzaEstrazione(numeri){
  if(numeri.length < 2) return {score:0, dettagli:[]};
 
  const dettagli = [];
  let score = 0;
 
  const figs = detectBaseFigures(numeri);
  figs.forEach(f => {
    score += f.type === 'Opposti' ? 3 :
             f.type === 'Triangolo' ? 4 :
             f.type === 'Quadrato' ? 5 : 2;
    dettagli.push({tipo:'figura', testo:`✓ ${f.type}: ${f.numbers.join('-')}`});
  });
 
  for(let i=0; i<numeri.length; i++){
    for(let j=i+1; j<numeri.length; j++){
      const diff = Math.abs(numeri[i] - numeri[j]);
      
      if(diff === 45){
        score += 2;
        dettagli.push({tipo:'distanza', testo:`✓ Opposti (Δ45): ${numeri[i]}-${numeri[j]}`});
      } else if(diff === 30 || diff === 60){
        score += 1.5;
        dettagli.push({tipo:'distanza', testo:`✓ Trigono (Δ${diff}): ${numeri[i]}-${numeri[j]}`});
      } else if(diff === 15 || diff === 75){
        score += 1;
        dettagli.push({tipo:'distanza', testo:`✓ Sesto (Δ${diff}): ${numeri[i]}-${numeri[j]}`});
      }
    }
  }
 
  numeri.forEach(n => {
    const ds = String(n).split('').reduce((s,d)=>s+ +d,0);
    const ridotto = ds > 9 ? ds - 9 : ds;
    if(numeri.includes(ridotto) && ridotto !== n){
      score += 0.5;
      dettagli.push({tipo:'somma', testo:`✓ Somma cifre: ${n} → ${ridotto}`});
    }
  });
 
  return {score, dettagli: dettagli.slice(0,10)};
}

function analizzaCoerenzaEstrazioneUI(numeri){
  const analisi = analizzaCoerenzaEstrazione(numeri);
  const box = document.getElementById('coerenzaBox');
 
  if(!analisi.dettagli.length){
    box.innerHTML = '';
    return;
  }
 
  let html = `<div class="coerenza-box">`;
  html += `<div style="color:var(--brass-hi);font-weight:600;margin-bottom:6px;">Coerenza geometrica: <span style="color:#4fae7a;">${analisi.score.toFixed(1)}</span></div>`;
  analisi.dettagli.forEach(d => {
    html += `<div class="coerenza-item">${d.testo}</div>`;
  });
  html += `</div>`;
 
  box.innerHTML = html;
}

// ============================================================
// VERIFICA A RITROSO (BACKTEST)
// ============================================================
function popolaBtRuote(){
  document.getElementById('btRuotaSelect').innerHTML =
    Object.entries(RUOTE_NOME).map(([k,v])=>`<option value="${k}">${k} - ${v}</option>`).join('');
}
function setupBtDateRange(){
  const inp = document.getElementById('btDateInput');
  if(archivio && archivio.righe.length){
    const dates = archivio.righe.map(r=>parseData(r.data)).filter(Boolean).sort((a,b)=>a-b);
    inp.min = toISOData(dates[0]);
    inp.max = toISOData(dates[dates.length-1]);
    if(!inp.value) inp.value = toISOData(dates[dates.length-1]);
  } else {
    if(!inp.value) inp.value = toISOData(new Date());
  }
}
function computeBacktestCandidates(baseEstrazioni){
  const n = baseEstrazioni.length;
  if(!n) return [];
 
  const lastSeen = {};
  const freq = new Array(91).fill(0);
 
  baseEstrazioni.forEach((r, idx)=>{
    [r.n1,r.n2,r.n3,r.n4,r.n5].forEach(x=>{
      lastSeen[x]=idx;
      freq[x]++;
    });
  });
 
  const lastDraw = baseEstrazioni[n-1];
  const lastNums = [lastDraw.n1,lastDraw.n2,lastDraw.n3,lastDraw.n4,lastDraw.n5];
  const scores = [];
 
  for(let num=1; num<=90; num++){
    const delay = (lastSeen[num]!==undefined) ? (n-1-lastSeen[num]) : n;
    const frequenza = freq[num] / n;
    let bonus = 0;
    const motivi = [];
    
    lastNums.forEach(ln=>{
      if(ln===num) return;
      if(rot(ln,45)===num){ bonus+=15; motivi.push(`opposto ${ln}`); }
      else if(rot(ln,30)===num){ bonus+=8; motivi.push(`trigono ${ln}`); }
      else if(rot(ln,60)===num){ bonus+=8; motivi.push(`trigono ${ln}`); }
      else if(rot(ln,15)===num){ bonus+=5; motivi.push(`sesto ${ln}`); }
    });
    
    if(frequenza > 0.15){
      bonus += 5;
      motivi.push('frequente');
    }
    
    const scoreFinale = delay + bonus + (frequenza * 10);
    
    scores.push({ n:num, delay, score:scoreFinale, frequenza, motivi });
  }
 
  scores.sort((a,b)=> b.score-a.score || b.delay-a.delay);
  return scores;
}
function runBacktest(){
  if(!archivio || !archivio.righe.length){
    showNotification('⚠️ Carica prima l\'archivio (Carica ZIP Locale)');
    return;
  }
  const ruota = document.getElementById('btRuotaSelect').value;
  const dateStr = document.getElementById('btDateInput').value;
  if(!dateStr){ showNotification('⚠️ Seleziona una data dal calendario'); return; }
  const dSel = parseData(dateStr);
  const nBase = parseInt(document.getElementById('btNumBase').value)||60;
  const topN = parseInt(document.getElementById('btTopN').value)||10;
  const sub = archivio.righe.filter(r=>r.ruota===ruota).sort((a,b)=>parseData(a.data)-parseData(b.data));
  if(!sub.length){ showNotification('⚠️ Nessuna estrazione per questa ruota'); return; }
  const upTo = sub.filter(r=>parseData(r.data) <= dSel);
  if(upTo.length < 5){ showNotification('⚠️ Troppo poche estrazioni fino a questa data'); return; }
  const base = upTo.slice(-nBase);
  const exact = sub.find(r => parseData(r.data).getTime() === dSel.getTime());
  const precedente = upTo[upTo.length-1];
  const after = sub.filter(r=>parseData(r.data) > dSel);
  const nextDraw = after.length ? after[0] : null;
  const candidates = computeBacktestCandidates(base);
  const topCand = candidates.slice(0, topN);
  LAST_BACKTEST = topCand;
  renderBacktestResult({
    ruota, dSel, base, exact, precedente, nextDraw, topCand,
    precISO: precedente ? toISOData(parseData(precedente.data)) : '',
    nextISO: nextDraw ? toISOData(parseData(nextDraw.data)) : ''
  });
  renderNumeriPromettenti();
}
function renderBacktestResult(d){
  const box = document.getElementById('btResults');
  const nome = CODICE_TO_NOME[d.ruota];
  const actualNums = d.nextDraw ? [d.nextDraw.n1,d.nextDraw.n2,d.nextDraw.n3,d.nextDraw.n4,d.nextDraw.n5] : [];
  const topNums = d.topCand.map(c=>c.n);
  const hits = actualNums.filter(n=>topNums.includes(n));
  let html = `<div class="bt-result">`;
  html += `<div style="color:var(--brass-hi);font-weight:600;margin-bottom:4px;">📊 ${nome} (${d.ruota}) — verifica al ${d.dSel.toLocaleDateString('it-IT')}</div>`;
  html += `<div style="color:#c9b98f;">Base: ${d.base.length} estrazioni (${d.base[0].data} → ${d.base[d.base.length-1].data})</div>`;
  if(d.exact){
    html += `<div class="bt-label">Estrazione esatta del giorno ${d.exact.data}:</div><div class="bt-row">`;
    [d.exact.n1,d.exact.n2,d.exact.n3,d.exact.n4,d.exact.n5].forEach(n=>{ html += `<span class="bt-num actual">${n}</span>`; });
    html += `</div>`;
  } else {
    html += `<div style="margin-top:8px;padding:8px;background:rgba(224,176,74,.15);border-radius:6px;color:#e0b04a;font-size:11.5px;line-height:1.6;">
      ⚠️ <b>Nessuna estrazione in data ${d.dSel.toLocaleDateString('it-IT')}</b> per ${nome}.<br>
      Ultima precedente usata come base: <b>${d.precedente.data}</b><br>
      <span class="bt-jump" data-data="${d.precISO}">◀ vai alla data precedente</span> ·
      ${d.nextISO?`<span class="bt-jump" data-data="${d.nextISO}">vai alla successiva ▶</span>`:''}
    </div>`;
  }
  html += `<div class="bt-label">Top ${d.topCand.length} numeri previsti (ritardatari + figure + frequenza):</div><div class="bt-row">`;
  d.topCand.forEach(c=>{
    const isHit = actualNums.includes(c.n);
    const title = `ritardo ${c.delay} · freq ${(c.frequenza*100).toFixed(1)}%${c.motivi.length?` · ${c.motivi.join(', ')}`:''}`;
    html += `<span class="bt-num ${isHit?'hit':''}" title="${title}">${c.n}</span>`;
  });
  html += `</div>`;
  if(d.nextDraw){
    html += `<div class="bt-label">Estrazione reale successiva (${d.nextDraw.data}):</div><div class="bt-row">`;
    actualNums.forEach(n=>{
      const isHit = topNums.includes(n);
      html += `<span class="bt-num actual" style="${isHit?'background:var(--gold-fill);color:#1a1206;border:2px solid #4fae7a;':''}">${n}</span>`;
    });
    html += `</div>`;
    const pct = ((hits.length/5)*100).toFixed(0);
    html += `<div style="margin-top:8px;font-weight:600;color:${hits.length?'#4fae7a':'#e0a0a0'};">`;
    html += `🎯 Match: ${hits.length}/5 numeri previsti (${pct}%)${hits.length?` → ${hits.join(', ')}`:''}</div>`;
  } else {
    html += `<div style="margin-top:8px;color:#e0b04a;">ℹ️ Nessuna estrazione successiva disponibile (data troppo recente)</div>`;
  }
  html += `<div class="btn-row" style="margin-top:10px;">`;
  html += `<button class="step" id="btBringCand">🎯 Porta previsti sul Cerchio</button>`;
  if(d.nextDraw) html += `<button class="step" id="btBringActual">📗 Porta estrazione reale</button>`;
  html += `</div></div>`;
  box.innerHTML = html;
  box.querySelectorAll('.bt-jump').forEach(s=>{
    s.addEventListener('click',()=>{ document.getElementById('btDateInput').value = s.dataset.data; runBacktest(); });
  });
  document.getElementById('btBringCand').addEventListener('click', ()=>{
    drawHighlighted.clear(); topNums.forEach(n=>drawHighlighted.add(n));
    renderDrawChips(); renderAll(); showNotification(`🎯 ${topNums.length} numeri previsti sul Cerchio`);
  });
  if(d.nextDraw){
    document.getElementById('btBringActual').addEventListener('click', ()=>{
      drawHighlighted.clear(); actualNums.forEach(n=>drawHighlighted.add(n));
      renderDrawChips(); renderAll(); showNotification('📗 Estrazione reale sul Cerchio');
    });
  }
}

// ============================================================
// NUMERI PROMETTENTI (SCORING AVANZATO)
// ============================================================
function calcolaNumeriPromettenti(){
  const mappa = new Map();
 
  const addFonte = (n, score, fonte, peso=1)=>{
    if(!mappa.has(n)) mappa.set(n, {n, score:0, fonti:new Map(), dettagli:{}});
    const e = mappa.get(n);
    e.score += score * peso;
    const count = e.fonti.get(fonte) || 0;
    e.fonti.set(fonte, count + 1);
  };
 
  (ENGINE.candidates||[]).slice(0,15).forEach(c=>{
    addFonte(c.n, Math.min(100, c.score), 'motore', 1.0);
  });
 
  (CONVERGENCE||[]).slice(0,15).forEach(c=>{
    const peso = Math.min(1.5, 1 + (c.drawCount - 2) * 0.1);
    addFonte(c.n, Math.min(100, c.score), 'conv', peso);
  });
 
  (LAST_BACKTEST||[]).slice(0,15).forEach((c,i)=>{
    const peso = 1.2 - (i * 0.05);
    addFonte(c.n, Math.min(100, c.score), 'backtest', Math.max(0.5, peso));
  });
 
  return [...mappa.values()]
    .map(e => ({
      ...e,
      scoreFinale: e.score * (1 + (e.fonti.size - 1) * 0.25)
    }))
    .sort((a,b) => b.scoreFinale - a.scoreFinale || b.fonti.size - a.fonti.size || a.n - b.n)
    .slice(0,12);
}
function renderNumeriPromettenti(){
  const box = document.getElementById('esitiContent');
  const risultati = calcolaNumeriPromettenti();
  if(!risultati.length){
    box.innerHTML = `<div class="esiti-vuoto">Esegui almeno un'analisi (⚡ Motore, 🔀 Convergenze o 🕰️ Verifica a Ritroso)<br>poi premi <b>↻ Aggiorna</b>.</div>`;
    return;
  }
  const maxScore = risultati[0].scoreFinale || 1;
  box.innerHTML = `
    <div class="esiti-grid">
      ${risultati.map((r,i)=>`
        <div class="esito-item">
          <div class="esito-num ${i===0?'top1':''}" data-n="${r.n}"
               title="Score aggregato ${r.scoreFinale.toFixed(1)} — Fonti: ${[...r.fonti.keys()].join(', ')}">
            <span class="n">${r.n}</span>
            <span class="s">${(r.scoreFinale/maxScore*100).toFixed(0)}</span>
          </div>
          <div class="esito-fonti">
            ${r.fonti.has('motore')  ?'<span class="fonte-dot fd-motore"  title="Motore Previsivo"></span>':''}
            ${r.fonti.has('conv')    ?'<span class="fonte-dot fd-conv"    title="Convergenze"></span>':''}
            ${r.fonti.has('backtest')?'<span class="fonte-dot fd-backtest" title="Verifica a Ritroso"></span>':''}
          </div>
        </div>`).join('')}
    </div>
    <div class="esiti-legenda">
      <span><span class="fonte-dot fd-motore"></span>Motore</span>
      <span><span class="fonte-dot fd-conv"></span>Convergenze</span>
      <span><span class="fonte-dot fd-backtest"></span>Backtest</span>
    </div>`;
  box.querySelectorAll('.esito-num').forEach(el=>{
    el.addEventListener('click',()=>{
      const n = parseInt(el.dataset.n);
      highlighted.add(n); renderChips(); renderAll();
      showNotification(`🎯 ${n} evidenziato sul Cerchio`);
    });
  });
}

// ============================================================
// CERCHIO DI SPILIMBERGO: BASE
// ============================================================
const CX = 350, CY = 350, SECTORS = 32, SECTOR_ANGLE = 360/SECTORS, HALF = SECTOR_ANGLE/2;
const REST_OFFSET = ((-HALF)%360+360)%360;
const RINGS = [
  { key:'r0', numbers:[1,24,47,70,3,26,49,72,5,28,51,74,7,30,53,76,9,32,55,78,11,34,57,80,13,36,59,82,15,38], rIn:76, rOut:150, fs:16 },
  { key:'r1', numbers:[61,84,17,40,63,86,19,42,65,88,21,44,67,90,23,46,69,2,25,48,71,4,27,50,73,6,29,52,75,8], rIn:158, rOut:222, fs:18 },
  { key:'r2', numbers:[31,54,77,10,33,56,79,12,35,58,81,14,37,60,83,16,39,62,85,18,41,64,87,20,43,66,89,22,45,68], rIn:230, rOut:284, fs:20 }
];
const offset = [REST_OFFSET, REST_OFFSET, REST_OFFSET];
const highlighted = new Set(), drawHighlighted = new Set();
const svg = document.getElementById('wheel');
const NS = 'http://www.w3.org/2000/svg';
const ENGINE = { figures:[], candidates:[], base:[] };
let overlayG = null, heatOn = false, heatMap = new Map(), tourTimer = null;
const HISTORY = [];
let CONVERGENCE = [];

function rot(n, step){ return ((n-1+step)%90)+1; }
function showNotification(msg, dur=2500){
  const n = document.getElementById('notification');
  n.textContent = msg; n.classList.add('show');
  setTimeout(()=>n.classList.remove('show'), dur);
}
function el(tag, attrs){
  const e = document.createElementNS(NS, tag);
  for(const k in attrs) e.setAttribute(k, attrs[k]);
  return e;
}
function normalizeDeg(d){ d = d%360; if(d<0) d+=360; return d; }
function polar(cx, cy, r, ang){
  const a = (ang-90)*Math.PI/180;
  return [cx+r*Math.cos(a), cy+r*Math.sin(a)];
}
function annulusPath(cx,cy,rin,rout){
  return `M ${cx-rout} ${cy} A ${rout} ${rout} 0 1 0 ${cx+rout} ${cy} A ${rout} ${rout} 0 1 0 ${cx-rout} ${cy} Z M ${cx-rin} ${cy} A ${rin} ${rin} 0 1 1 ${cx+rin} ${cy} A ${rin} ${rin} 0 1 1 ${cx-rin} ${cy} Z`;
}

// ============================================================
// CERCHIO: COSTRUZIONE E RENDER
// ============================================================
function buildStatic(){
  const grad = el('radialGradient',{id:'paperGrad',cx:'45%',cy:'40%',r:'70%'});
  grad.appendChild(el('stop',{offset:'0%','stop-color':'#f8f1de'}));
  grad.appendChild(el('stop',{offset:'75%','stop-color':'#efe3c4'}));
  grad.appendChild(el('stop',{offset:'100%','stop-color':'#ddceac'}));
  const defs = el('defs',{}); defs.appendChild(grad); svg.appendChild(defs);
  svg.appendChild(el('circle',{cx:CX,cy:CY,r:332,fill:'url(#paperGrad)',stroke:'var(--brass)','stroke-width':3}));
  svg.appendChild(el('circle',{cx:CX,cy:CY,r:326,fill:'none',stroke:'var(--brass)','stroke-width':1,opacity:.6}));
  const dSize = 46;
  svg.appendChild(el('path',{d:`M ${CX} ${CY-dSize} L ${CX+dSize} ${CY} L ${CX} ${CY+dSize} L ${CX-dSize} ${CY} Z`,fill:'#00000007',stroke:'var(--brass)','stroke-width':1.3,opacity:.75}));
  svg.appendChild(el('circle',{cx:CX,cy:CY,r:3,fill:'var(--ink)'}));
}
const COMPASS_DEFS = [['N',HALF],['E',90+HALF],['S',180+HALF],['O',270+HALF]];
function buildCompass(ring2){
  const compassTexts = [];
  COMPASS_DEFS.forEach(([label,ang])=>{
    const [x1,y1] = polar(CX,CY,ring2.ring.rOut+12,ang);
    const [x2,y2] = polar(CX,CY,ring2.ring.rOut+26,ang);
    ring2.g.appendChild(el('line',{x1,y1,x2,y2,stroke:'var(--ink)','stroke-width':2.5}));
    const t = el('text',{class:'compass-label'}); t.textContent = label;
    ring2.textG.appendChild(t);
    compassTexts.push({el:t, ang});
  });
  ring2.compassTexts = compassTexts;
}
const ringGroups = [];
function buildRing(ring, idx){
  const g = el('g',{id:'lines-'+idx});
  g.appendChild(el('circle',{cx:CX,cy:CY,r:ring.rIn, class:'band'}));
  g.appendChild(el('circle',{cx:CX,cy:CY,r:ring.rOut,class:'band'}));
  for(let s=0;s<SECTORS;s++){
    const ang = s*SECTOR_ANGLE;
    const [x1,y1] = polar(CX,CY,ring.rIn,ang);
    const [x2,y2] = polar(CX,CY,ring.rOut,ang);
    g.appendChild(el('line',{x1,y1,x2,y2,class:'divider'}));
  }
  for(let s=30;s<32;s++){
    const a1=s*SECTOR_ANGLE, a2=(s+1)*SECTOR_ANGLE;
    const [ox1,oy1]=polar(CX,CY,ring.rOut,a1),[ox2,oy2]=polar(CX,CY,ring.rOut,a2);
    const [ix2,iy2]=polar(CX,CY,ring.rIn,a2),[ix1,iy1]=polar(CX,CY,ring.rIn,a1);
    g.appendChild(el('path',{d:`M ${ox1} ${oy1} A ${ring.rOut} ${ring.rOut} 0 0 1 ${ox2} ${oy2} L ${ix2} ${iy2} A ${ring.rIn} ${ring.rIn} 0 0 0 ${ix1} ${iy1} Z`,class:'gap-mark'}));
  }
  svg.appendChild(g);
  const textG = el('g',{id:'text-'+idx});
  const dots=[], drawDots=[];
  const texts = ring.numbers.map(n=>{
    const ddot = el('circle',{r:18,class:'draw-dot'}); textG.appendChild(ddot); drawDots.push(ddot);
    const dot = el('circle',{r:13,class:'highlight-dot'}); textG.appendChild(dot); dots.push(dot);
    const t = el('text',{class:'num','font-size':ring.fs,'data-number':n,'data-ring':idx});
    t.textContent = n; textG.appendChild(t);
    return t;
  });
  svg.appendChild(textG);
  const hit = el('path',{class:'ring-track','data-ring':idx,fill:'#fff',opacity:0});
  hit.setAttribute('d',annulusPath(CX,CY,ring.rIn-4,ring.rOut+4));
  hit.style.pointerEvents='fill'; svg.appendChild(hit);
  ringGroups.push({g,textG,texts,dots,drawDots,hit,ring,idx});
}
function renderRing(idx){
  const rg = ringGroups[idx];
  rg.g.setAttribute('transform',`rotate(${offset[idx]} ${CX} ${CY})`);
  const rMid = (rg.ring.rIn+rg.ring.rOut)/2;
  rg.texts.forEach((t,i)=>{
    const ang = (i+0.5)*SECTOR_ANGLE + offset[idx];
    const [x,y] = polar(CX,CY,rMid,ang);
    t.setAttribute('x',x); t.setAttribute('y',y);
    const num = rg.ring.numbers[i];
    t.classList.toggle('highlighted', highlighted.has(num));
    t.classList.toggle('draw-hit', drawHighlighted.has(num));
    const dot = rg.dots[i]; dot.setAttribute('cx',x); dot.setAttribute('cy',y);
    dot.style.opacity = highlighted.has(num)?1:0;
    const ddot = rg.drawDots[i]; ddot.setAttribute('cx',x); ddot.setAttribute('cy',y);
    ddot.style.opacity = drawHighlighted.has(num)?1:0;
  });
  if(rg.compassTexts) rg.compassTexts.forEach(ct=>{
    const [x,y] = polar(CX,CY,rg.ring.rOut+34, ct.ang+offset[idx]);
    ct.el.setAttribute('x',x); ct.el.setAttribute('y',y);
  });
  updateReadout(); updateDistancePanel(); updateFigureList(); drawOppositeLines(); drawFigureOverlay(); applyHeat();
}
function compassAngles(){
  const n = normalizeDeg(offset[2]+HALF);
  return { N:n, E:normalizeDeg(n+90), S:normalizeDeg(n+180), O:normalizeDeg(n+270) };
}
function sectorAt(ringIdx, compassAngle){
  const rel = normalizeDeg(compassAngle-offset[ringIdx]);
  let idx = Math.round(rel/SECTOR_ANGLE-0.5);
  return ((idx%SECTORS)+SECTORS)%SECTORS;
}
function valueAt(ringIdx, compassAngle){
  const idx = sectorAt(ringIdx, compassAngle);
  const nums = RINGS[ringIdx].numbers;
  return idx<nums.length ? nums[idx] : null;
}
function updateReadout(){
  const dirs = compassAngles();
  for(let r=0;r<3;r++) for(const d in dirs){
    const v = valueAt(r,dirs[d]);
    const cell = document.getElementById(`r${r}-${d}`);
    if(v===null){ cell.textContent='—'; cell.classList.add('blank'); }
    else { cell.textContent=v; cell.classList.remove('blank'); }
  }
}
function snap(idx){
  offset[idx] = normalizeDeg(Math.round((offset[idx]-HALF)/SECTOR_ANGLE)*SECTOR_ANGLE+HALF);
  renderRing(idx);
}
function stepRing(idx,dir){
  offset[idx] = normalizeDeg(offset[idx]+dir*SECTOR_ANGLE);
  renderRing(idx);
}
let dragging = null;
function angleFromEvent(evt){
  const pt = svg.createSVGPoint(); pt.x = evt.clientX; pt.y = evt.clientY;
  const loc = pt.matrixTransform(svg.getScreenCTM().inverse());
  return { ang: Math.atan2(loc.y-CY,loc.x-CX)*180/Math.PI };
}
svg.addEventListener('pointerdown',e=>{
  const target = e.target;
  if(!target.classList.contains('ring-track')) return;
  const idx = parseInt(target.getAttribute('data-ring'));
  const p = angleFromEvent(e);
  dragging = { idx, startAngle:p.ang, startOffset:offset[idx], moved:false, startClientX:e.clientX, startClientY:e.clientY };
  target.classList.add('dragging');
  svg.setPointerCapture(e.pointerId);
  e.preventDefault();
});
svg.addEventListener('pointermove',e=>{
  if(!dragging) return;
  if(Math.hypot(e.clientX-dragging.startClientX,e.clientY-dragging.startClientY)>4) dragging.moved = true;
  const p = angleFromEvent(e);
  offset[dragging.idx] = dragging.startOffset+(p.ang-dragging.startAngle);
  renderRing(dragging.idx);
});
function endDrag(e){
  if(!dragging) return;
  const idx = dragging.idx, wasDrag = dragging.moved;
  document.querySelectorAll('.ring-track').forEach(t=>t.classList.remove('dragging'));
  if(wasDrag) snap(idx);
  else {
    const p = angleFromEvent(e);
    const sIdx = sectorAt(idx, normalizeDeg(p.ang+90));
    const nums = RINGS[idx].numbers;
    if(sIdx<nums.length) toggleHighlight(nums[sIdx]);
    renderRing(idx);
  }
  dragging = null;
}
svg.addEventListener('pointerup',endDrag);
svg.addEventListener('pointercancel',endDrag);

// ============================================================
// DISTANZE E FIGURE
// ============================================================
function calculateDistances(){
  if(highlighted.size<2) return [];
  const nums = [...highlighted].sort((a,b)=>a-b);
  const res = [];
  for(let i=0;i<nums.length;i++) for(let j=i+1;j<nums.length;j++){
    const diff = Math.abs(nums[i]-nums[j]);
    res.push({pair:`${nums[i]}-${nums[j]}`, dist:diff, comp:90-diff});
  }
  return res;
}
function updateDistancePanel(){
  const list = document.getElementById('distanceList');
  const d = calculateDistances();
  if(!d.length){ list.innerHTML = '<div style="text-align:center;color:#8a7a5c;font-size:12px;padding:8px;">Evidenzia almeno 2 numeri</div>'; return; }
  list.innerHTML = d.map(x=>`<div class="distance-item"><span style="color:var(--brass-hi);font-weight:600;">${x.pair}</span><span style="color:#c9b98f;">Δ ${x.dist}</span><span style="color:#b3a37e;font-style:italic;">C ${x.comp}</span></div>`).join('');
}
function detectGeometricFigures(){
  const figs = [], S = new Set([...highlighted]);
  [...highlighted].forEach(n=>{
    const o = rot(n,45);
    if(S.has(o)&&n<o) figs.push({type:'Opposti 180°',numbers:[n,o],color:'#c0392b'});
    const t1=rot(n,30),t2=rot(n,60);
    if(S.has(t1)&&S.has(t2)&&n<t1&&t1<t2) figs.push({type:'Triangolo 120°',numbers:[n,t1,t2],color:'#2e8b57'});
    const e1=rot(n,15),e2=rot(n,30);
    if(S.has(e1)&&S.has(e2)&&n<e1&&e1<e2) figs.push({type:'Terzina 60°',numbers:[n,e1,e2],color:'#d98e2b'});
    const q1=rot(n,22),q2=rot(n,45),q3=rot(n,67);
    if(S.has(q1)&&S.has(q2)&&S.has(q3)&&n<q1&&q1<q2)
      figs.push({type:'Quadrato 90°',numbers:[n,q1,q2,q3],color:'#2980b9'});
  });
  return figs;
}
function updateFigureList(){
  document.getElementById('figureList').innerHTML = detectGeometricFigures().map(f=>`<div class="figure-item"><span class="type">${f.type}</span><span>${f.numbers.join(' - ')}</span></div>`).join('');
}
function findNumberPosition(num){
  for(let idx=0;idx<RINGS.length;idx++){
    const ring = RINGS[idx];
    const pos = ring.numbers.indexOf(num);
    if(pos!==-1){
      const rMid = (ring.rIn+ring.rOut)/2;
      const ang = (pos+0.5)*SECTOR_ANGLE+offset[idx];
      const [x,y] = polar(CX,CY,rMid,ang);
      return {x,y};
    }
  }
  return null;
}
function drawOppositeLines(){
  document.querySelectorAll('.opposite-line').forEach(l=>l.remove());
  const nums = [...highlighted];
  nums.forEach(n=>{
    const o = rot(n,45);
    if(nums.includes(o)&&n<o){
      const p1=findNumberPosition(n),p2=findNumberPosition(o);
      if(p1&&p2) svg.insertBefore(el('line',{x1:p1.x,y1:p1.y,x2:p2.x,y2:p2.y,class:'opposite-line'}),svg.querySelector('#lines-0'));
    }
  });
}

// ============================================================
// FIGURE GEOMETRICHE REALI
// ============================================================
function angleOfPoint(x, y){
  return (Math.atan2(y - CY, x - CX) * 180 / Math.PI + 90 + 360) % 360;
}
function midPoint(pts){
  const mx = pts.reduce((s,p)=>s+p.x,0)/pts.length;
  const my = pts.reduce((s,p)=>s+p.y,0)/pts.length;
  return {x:mx, y:my};
}
function arcBetween(p1, p2, r){
  const a1 = angleOfPoint(p1.x, p1.y);
  const a2 = angleOfPoint(p2.x, p2.y);
  let start = a1, end = a2;
  let diff = (end - start + 360) % 360;
  let large = diff > 180 ? 1 : 0;
  let sweep = 1;
  if(diff > 180){
    [start, end] = [end, start];
    diff = (end - start + 360) % 360;
    large = diff > 180 ? 1 : 0;
  }
  const s = polar(CX, CY, r, start);
  const e = polar(CX, CY, r, end);
  return `M ${s[0]} ${s[1]} A ${r} ${r} 0 ${large} ${sweep} ${e[0]} ${e[1]}`;
}
function polygonPath(pts){
  return pts.map((p,i)=>`${i?'L':'M'} ${p.x} ${p.y}`).join(' ')+' Z';
}
function addLabel(cx, cy, text, color){
  const pad = 5;
  const w = text.length * 6.5 + pad*2;
  const bg = el('rect',{x:cx-w/2, y:cy-9, width:w, height:18, rx:9,
    fill:'rgba(0,0,0,.6)', stroke:color, 'stroke-width':1});
  const lab = el('text',{x:cx, y:cy+0.5, class:'fig-label', fill:'#fff'});
  lab.textContent = text;
  overlayG.appendChild(bg);
  overlayG.appendChild(lab);
}
function drawVertex(p, color, r=6){
  overlayG.appendChild(el('circle',{cx:p.x, cy:p.y, r:r, class:'fig-vertex',
    fill:color, stroke:'#fff'}));
  overlayG.appendChild(el('circle',{cx:p.x, cy:p.y, r:r+4, class:'fig-vertex-ring',
    stroke:color}));
}
function drawRays(pts, color){
  pts.forEach(p=>{
    overlayG.appendChild(el('line',{x1:CX, y1:CY, x2:p.x, y2:p.y,
      class:'fig-ray', stroke:color}));
  });
}

function drawFigOpposti(pts, color, label){
  const [p1, p2] = pts;
  overlayG.appendChild(el('line',{x1:p1.x, y1:p1.y, x2:p2.x, y2:p2.y,
    class:'fig-diameter fig-pulse', stroke:color}));
  const rOut = 295;
  overlayG.appendChild(el('path',{d:arcBetween(p1, p2, rOut),
    class:'fig-arc fig-pulse', stroke:color}));
  overlayG.appendChild(el('path',{d:arcBetween(p2, p1, rOut),
    class:'fig-arc-inner', stroke:color}));
  const cr = 8;
  overlayG.appendChild(el('circle',{cx:CX, cy:CY, r:cr, class:'fig-center',
    fill:'none', stroke:color, 'stroke-width':2}));
  overlayG.appendChild(el('line',{x1:CX-cr, y1:CY, x2:CX+cr, y2:CY,
    class:'fig-crosshair', stroke:color}));
  overlayG.appendChild(el('line',{x1:CX, y1:CY-cr, x2:CX, y2:CY+cr,
    class:'fig-crosshair', stroke:color}));
  drawVertex(p1, color); drawVertex(p2, color);
  const mid = midPoint(pts);
  const dx = mid.x - CX, dy = mid.y - CY;
  const dist = Math.hypot(dx,dy) || 1;
  const lx = CX + dx/dist * 60, ly = CY + dy/dist * 60;
  addLabel(lx, ly, label, color);
  const ang = el('text',{x:CX, y:CY+28, class:'fig-angle-label', fill:color});
  ang.textContent = '180°';
  overlayG.appendChild(ang);
}

function drawFigTriangolo(pts, color, label){
  overlayG.appendChild(el('path',{d:polygonPath(pts), class:'fig-shape fig-pulse',
    fill:color, stroke:color}));
  drawRays(pts, color);
  const rOut = 295;
  overlayG.appendChild(el('path',{d:arcBetween(pts[0], pts[1], rOut),
    class:'fig-arc', stroke:color}));
  overlayG.appendChild(el('path',{d:arcBetween(pts[1], pts[2], rOut),
    class:'fig-arc', stroke:color}));
  overlayG.appendChild(el('path',{d:arcBetween(pts[2], pts[0], rOut),
    class:'fig-arc', stroke:color}));
  pts.forEach(p=>drawVertex(p, color));
  const c = midPoint(pts);
  overlayG.appendChild(el('circle',{cx:c.x, cy:c.y, r:4, class:'fig-center',
    fill:color, stroke:'#fff', 'stroke-width':1.5}));
  addLabel(c.x, c.y - 18, label, color);
}

function drawFigTerzina(pts, color, label){
  const rOut = 295;
  overlayG.appendChild(el('path',{d:arcBetween(pts[0], pts[2], rOut),
    class:'fig-arc fig-pulse', stroke:color}));
  overlayG.appendChild(el('path',{d:polygonPath(pts), class:'fig-shape',
    fill:color, stroke:color}));
  drawRays(pts, color);
  const rIn = 70;
  overlayG.appendChild(el('path',{d:arcBetween(pts[0], pts[2], rIn),
    class:'fig-arc-inner', stroke:color}));
  pts.forEach(p=>drawVertex(p, color, 5));
  const c = midPoint(pts);
  overlayG.appendChild(el('circle',{cx:c.x, cy:c.y, r:3.5, class:'fig-center',
    fill:color, stroke:'#fff', 'stroke-width':1.5}));
  addLabel(c.x, c.y - 16, label, color);
}

function drawFigQuadrato(pts, color, label){
  overlayG.appendChild(el('path',{d:polygonPath(pts), class:'fig-shape fig-pulse',
    fill:color, stroke:color}));
  drawRays(pts, color);
  overlayG.appendChild(el('line',{x1:pts[0].x, y1:pts[0].y, x2:pts[2].x, y2:pts[2].y,
    class:'fig-ray', stroke:color}));
  overlayG.appendChild(el('line',{x1:pts[1].x, y1:pts[1].y, x2:pts[3].x, y2:pts[3].y,
    class:'fig-ray', stroke:color}));
  const rOut = 295;
  for(let i=0;i<4;i++){
    overlayG.appendChild(el('path',{
      d:arcBetween(pts[i], pts[(i+1)%4], rOut),
      class:'fig-arc', stroke:color}));
  }
  pts.forEach(p=>drawVertex(p, color));
  const c = midPoint(pts);
  overlayG.appendChild(el('circle',{cx:c.x, cy:c.y, r:4, class:'fig-center',
    fill:color, stroke:'#fff', 'stroke-width':1.5}));
  addLabel(c.x, c.y - 18, label, color);
}

function drawFigDefault(pts, color, label){
  overlayG.appendChild(el('path',{d:polygonPath(pts), class:'fig-shape',
    fill:color, stroke:color}));
  drawRays(pts, color);
  pts.forEach(p=>drawVertex(p, color, 5));
  const c = midPoint(pts);
  addLabel(c.x, c.y - 16, label, color);
}

function drawFigureOverlay(){
  if(!overlayG) return;
  overlayG.innerHTML = '';
  ENGINE.figures.forEach(f=>{
    const pts = f.numbers.map(findNumberPosition);
    if(pts.some(p=>!p)) return;
    if(pts.length < 2) return;
    switch(f.type){
      case 'Opposti':      drawFigOpposti(pts, f.color, 'Opposti 180°'); break;
      case 'Triangolo':    drawFigTriangolo(pts, f.color, 'Triangolo 120°'); break;
      case 'Terzina 60°':  drawFigTerzina(pts, f.color, 'Terzina 60°'); break;
      case 'Quadrato':     drawFigQuadrato(pts, f.color, 'Quadrato 90°'); break;
      default:             drawFigDefault(pts, f.color, f.type);
    }
  });
}

// ============================================================
// MOTORE PREVISIVO L1
// ============================================================
function getBase(){ return drawHighlighted.size>=2 ? [...drawHighlighted] : [...highlighted]; }
function detectBaseFigures(nums){
  const figs = [], S = new Set(nums);
  nums.forEach(n=>{
    const o = rot(n,45);
    if(S.has(o)&&n<o) figs.push({type:'Opposti',numbers:[n,o],color:'#c0392b'});
    const t1=rot(n,30),t2=rot(n,60);
    if(S.has(t1)&&S.has(t2)&&n<t1&&t1<t2) figs.push({type:'Triangolo',numbers:[n,t1,t2],color:'#2e8b57'});
    const e1=rot(n,15),e2=rot(n,30);
    if(S.has(e1)&&S.has(e2)&&n<e1&&e1<e2) figs.push({type:'Terzina 60°',numbers:[n,e1,e2],color:'#d98e2b'});
    const q1=rot(n,22),q2=rot(n,45),q3=rot(n,67);
    if(S.has(q1)&&S.has(q2)&&S.has(q3)&&n<q1&&q1<q2)
      figs.push({type:'Quadrato',numbers:[n,q1,q2,q3],color:'#2980b9'});
  });
  return figs;
}
function computeCandidates(base){
  const scores = new Map();
  const add = (n,peso,motivo)=>{
    if(base.includes(n)) return;
    if(!scores.has(n)) scores.set(n,{n,score:0,motivi:[]});
    const c = scores.get(n);
    c.score += peso;
    if(!c.motivi.includes(motivo)) c.motivi.push(motivo);
  };
 
  base.forEach(n=>{
    add(rot(n,45),3.0,`opposto di ${n}`);
    add(rot(n,30),2.0,`trigono di ${n}`);
    add(rot(n,60),2.0,`trigono di ${n}`);
    add(rot(n,15),1.5,`sesto di ${n}`);
    
    const ds = String(n).split('').reduce((s,d)=>s+ +d,0);
    const ridotto = ds > 9 ? ds - 9 : ds;
    add(ridotto,0.8,`somma cifre di ${n}`);
  });
 
  for(let i=0;i<base.length;i++) for(let j=i+1;j<base.length;j++){
    const a=base[i],b=base[j];
    
    const dc = Math.min(Math.abs(a-b),90-Math.abs(a-b));
    add(dc,1.2,`distanza di ${a}-${b}`);
    
    const s = a+b;
    add(s>90?s-90:s,1.2,`somma di ${a}+${b}`);
    
    if((a+b)%2===0){
      const m=(a+b)/2;
      add(m,1.5,`equidistante di ${a}-${b}`);
      add(rot(m,45),1.5,`opposto equidistante`);
    }
  }
 
  return [...scores.values()].sort((x,y)=>y.score-x.score||x.n-y.n);
}
function runEngine(){
  const base = getBase();
  if(base.length<2){ showNotification('⚠️ Carica prima almeno 2 numeri'); return; }
  ENGINE.base = base;
  ENGINE.candidates = computeCandidates(base);
  const top = ENGINE.candidates.slice(0,5).map(c=>c.n);
  const withTop = detectBaseFigures(base.concat(top)).filter(f=>f.numbers.some(n=>top.includes(n)));
  ENGINE.figures = detectBaseFigures(base).concat(withTop);
  renderEnginePanel(); drawFigureOverlay();
  if(heatOn) applyHeat();
  showNotification(`⚡ ${ENGINE.figures.length} figure, ${ENGINE.candidates.length} candidati`);
  renderNumeriPromettenti();
}
function renderEnginePanel(){
  const box = document.getElementById('engineResults');
  const c = ENGINE.candidates;
  if(!c.length){ box.innerHTML=''; return; }
  const pick = k=>c.slice(0,k).map(x=>x.n).join(' – ');
  const counts = {};
  ENGINE.figures.forEach(f=>counts[f.type]=(counts[f.type]||0)+1);
  const badges = Object.entries(counts).map(([t,k])=>{
    const col = ENGINE.figures.find(f=>f.type===t).color;
    return `<span class="fig-badge" style="border-color:${col};color:${col}">${k}× ${t}</span>`;
  }).join('');
  box.innerHTML = `
    <div style="margin-top:10px;">${badges||'<span style="font-size:12px;color:#8a7a5c">Nessuna figura</span>'}</div>
    <div class="pred-grid">
      <div class="pred-box"><div class="lbl">Amata</div><div class="val">${c[0]?.n??'—'}</div></div>
      <div class="pred-box"><div class="lbl">Ambo</div><div class="val" style="font-size:16px;">${pick(2)||'—'}</div></div>
      <div class="pred-box"><div class="lbl">Terno</div><div class="val" style="font-size:15px;">${pick(3)||'—'}</div></div>
      <div class="pred-box"><div class="lbl">Cinquina</div><div class="val" style="font-size:13px;">${pick(5)||'—'}</div></div>
    </div>
    <div class="cand-list">${c.slice(0,8).map(x=>`
      <div class="cand-item" data-n="${x.n}">
        <span class="cn">${x.n}</span>
        <span class="cm">${x.motivi.slice(0,2).join(' • ')}</span>
        <span class="cs">${x.score.toFixed(1)}</span>
      </div>`).join('')}
    </div>`;
  box.querySelectorAll('.cand-item').forEach(it=>{
    it.addEventListener('click',()=>{ highlighted.add(parseInt(it.dataset.n)); renderChips(); renderAll(); });
  });
}
function toggleHeat(){
  heatOn = !heatOn;
  if(heatOn&&ENGINE.candidates.length===0) runEngine();
  heatMap = new Map(ENGINE.candidates.slice(0,30).map((c,i)=>[c.n,1-i/30]));
  applyHeat();
  showNotification(heatOn?'🔥 Mappa calore attiva':'Mappa calore spenta');
}
function applyHeat(){
  ringGroups.forEach(rg=>{
    rg.texts.forEach((t,i)=>{
      const n = rg.ring.numbers[i];
      if(heatOn&&heatMap.has(n)){
        const k = heatMap.get(n);
        t.style.fill = `rgba(224,${Math.round(176-60*k)},40,${0.35+0.65*k})`;
        t.style.fontSize = (rg.ring.fs+2*k)+'px';
      } else { t.style.fill=''; t.style.fontSize=''; }
    });
  });
}
function startTour(){
  if(!ENGINE.figures.length){ showNotification('⚠️ Prima premi ⚡'); return; }
  stopTour();
  let i = 0;
  const out = document.getElementById('alignmentResult');
  const step = ()=>{
    const f = ENGINE.figures[i];
    out.innerHTML = `<span><strong>${i+1}/${ENGINE.figures.length}:</strong> ${f.type} → ${f.numbers.join(' – ')}</span>`;
    tourTimer = setTimeout(()=>{
      i++;
      if(i>=ENGINE.figures.length){ stopTour(); showNotification('🎬 Tour completato'); }
      else step();
    },2600);
  };
  step();
}
function stopTour(){ if(tourTimer){clearTimeout(tourTimer);tourTimer=null;} }
function clearEngine(){
  stopTour();
  ENGINE.figures=[];ENGINE.candidates=[];ENGINE.base=[];
  heatOn=false;heatMap.clear();applyHeat();drawFigureOverlay();
  document.getElementById('engineResults').innerHTML='';
  showNotification('🧹 Motore ripulito');
}

// ============================================================
// STORICO E CONVERGENZE L2
// ============================================================
const RUOTE_L2 = ['Bari','Cagliari','Firenze','Genova','Milano','Napoli','Palermo','Roma','Torino','Venezia','Nazionale'];
function parseHistory(text){
  const draws = [];
  text.split(/\n+/).map(l=>l.trim()).filter(Boolean).forEach(line=>{
    let ruota = null;
    for(const r of RUOTE_L2){ if(line.toLowerCase().includes(r.toLowerCase())){ ruota=r; break; } }
    const nums = [...new Set((line.match(/\d{1,2}/g)||[]).map(Number).filter(n=>n>=1&&n<=90))];
    if(nums.length) draws.push({ruota,numeri:nums.slice(0,5)});
  });
  return draws;
}
function loadHistoryFromTextarea(){
  const draws = parseHistory(document.getElementById('historyInput').value);
  if(!draws.length){ showNotification('⚠️ Nessuna estrazione valida'); return; }
  draws.forEach(d=>HISTORY.push(d));
  document.getElementById('historyInput').value='';
  renderHistList(); updateRuotaFilter();
  showNotification(`📥 ${draws.length} estrazioni caricate`);
}
function renderHistList(){
  const box = document.getElementById('histList');
  box.innerHTML = HISTORY.map((d,i)=>`
    <div class="hist-item">
      <span class="ruota">${d.ruota||'—'}</span>
      <span class="nums">${d.numeri.join(' ')}</span>
      ${d.data ? `<span style="font-size:10px;color:#8a7a5c;white-space:nowrap;">${d.data}</span>` : ''}
      <span class="del" data-i="${i}">✕</span>
    </div>`).join('');
  box.querySelectorAll('.del').forEach(x=>{
    x.addEventListener('click',()=>{ HISTORY.splice(parseInt(x.dataset.i),1); renderHistList(); updateRuotaFilter(); });
  });
}
function updateRuotaFilter(){
  const sel = document.getElementById('ruotaFilter');
  const ruote = [...new Set(HISTORY.map(d=>d.ruota).filter(Boolean))];
  sel.innerHTML = '<option value="all">Tutte le ruote</option>'+ruote.map(r=>`<option value="${r}">${r}</option>`).join('');
}
function runConvergence(){
  const filter = document.getElementById('ruotaFilter').value;
  const draws = filter==='all'?HISTORY:HISTORY.filter(d=>d.ruota===filter);
  if(draws.length<2){ showNotification('⚠️ Servono almeno 2 estrazioni'); return; }
  const conv = new Map();
  draws.forEach((draw,di)=>{
    computeCandidates(draw.numeri).forEach(c=>{
      if(!conv.has(c.n)) conv.set(c.n,{n:c.n,drawCount:0,score:0,motivi:[],ruote:new Set()});
      const e = conv.get(c.n);
      e.drawCount++; e.score += c.score;
      e.motivi.push(`estr.${di+1}: ${c.motivi[0]}`);
      if(draw.ruota) e.ruote.add(draw.ruota);
    });
  });
  CONVERGENCE = [...conv.values()].filter(e=>e.drawCount>=2).sort((a,b)=>b.drawCount-a.drawCount||b.score-a.score);
  renderConvergencePanel(draws.length);
  showNotification(`🔀 ${CONVERGENCE.length} convergenti su ${draws.length} estrazioni`);
  renderNumeriPromettenti();
}
function renderConvergencePanel(totalDraws){
  const box = document.getElementById('convResults');
  if(!CONVERGENCE.length){ box.innerHTML='<div style="text-align:center;color:#8a7a5c;font-size:12px;padding:8px;">Nessuna convergenza</div>'; return; }
  box.innerHTML = `<div style="font-size:12px;color:#c9b98f;margin:8px 0 6px;">Numeri "forti": candidati da più estrazioni</div>`+
    CONVERGENCE.slice(0,12).map(e=>`<div class="conv-item" data-n="${e.n}"><span class="cn">${e.n}</span><span class="conv-badge">in ${e.drawCount}/${totalDraws}</span><span class="cm">${[...e.ruote].join(', ')||'—'}</span><span class="cs">${e.score.toFixed(1)}</span></div>`).join('')+
    `<button class="step" id="convHighlight" style="margin-top:6px;">🎯 Evidenzia i convergenti</button>`;
  box.querySelectorAll('.conv-item').forEach(it=>{
    it.addEventListener('click',()=>{ highlighted.add(parseInt(it.dataset.n)); renderChips(); renderAll(); });
  });
  document.getElementById('convHighlight').addEventListener('click',()=>{
    CONVERGENCE.slice(0,8).forEach(e=>highlighted.add(e.n));
    renderChips(); renderAll();
    showNotification('🎯 Convergenti evidenziati');
  });
}
function loadHistoryDemo(){
  document.getElementById('historyInput').value = `Bari 7 23 45 68 81\nNapoli 12 34 56 72 90\nMilano 3 18 27 44 62\nBari 15 30 60 75 88\nRoma 9 27 45 54 72`;
  loadHistoryFromTextarea();
}
function clearHistory(){
  HISTORY.length=0;CONVERGENCE=[];
  renderHistList();updateRuotaFilter();
  document.getElementById('convResults').innerHTML='';
}

// ============================================================
// CARICA RUOTE DA ARCHIVIO PER CONVERGENZE
// ============================================================
function popolaConvArchivioRuote(){
  document.getElementById('convArchivioRuota').innerHTML =
    '<option value="all">🌐 Tutte le ruote</option>' +
    Object.entries(RUOTE_NOME).map(([k,v]) => `<option value="${k}">${k} - ${v}</option>`).join('');
}
function caricaRuoteDaArchivioPerConvergenze(){
  if(!archivio || !archivio.righe.length){
    showNotification('⚠️ Prima scarica o carica l\'archivio');
    return;
  }
  const codiceRuota = document.getElementById('convArchivioRuota').value;
  const nEstrazioni = parseInt(document.getElementById('convArchivioN').value) || 10;

  let righeFiltrate;
  if(codiceRuota === 'all'){
    righeFiltrate = archivio.righe;
  } else {
    righeFiltrate = archivio.righe.filter(r => r.ruota === codiceRuota);
  }
  if(!righeFiltrate.length){
    showNotification('⚠️ Nessuna estrazione trovata');
    return;
  }

  const perRuota = {};
  righeFiltrate.forEach(r => {
    if(!perRuota[r.ruota]) perRuota[r.ruota] = [];
    perRuota[r.ruota].push(r);
  });

  let aggiunte = 0, ruoteCaricate = 0;
  for(const [codice, righe] of Object.entries(perRuota)){
    righe.sort((a,b) => parseData(a.data) - parseData(b.data));
    const ultime = righe.slice(-nEstrazioni);
    const nomeRuota = CODICE_TO_NOME[codice];
    ultime.forEach(r => {
      const esiste = HISTORY.some(h => h.ruota === nomeRuota && h.data === r.data);
      if(!esiste){
        HISTORY.push({ ruota: nomeRuota, numeri:[r.n1,r.n2,r.n3,r.n4,r.n5], data:r.data });
        aggiunte++;
      }
    });
    ruoteCaricate++;
  }

  renderHistList();
  updateRuotaFilter();
  showNotification(`📥 ${aggiunte} estrazioni da ${ruoteCaricate} ruote caricate nelle Convergenze`);

  if(HISTORY.length >= 2){
    runConvergence();
  }
}

// ============================================================
// ALLINEAMENTI POTENZIATI
// ============================================================
function alignmentScoreFor(offsets, mode){
  const dirs = {N:offsets[2]+HALF, E:offsets[2]+HALF+90, S:offsets[2]+HALF+180, O:offsets[2]+HALF+270};
  let score = 0;
  for(let r=0;r<3;r++) for(const d in dirs){
    const rel = normalizeDeg(normalizeDeg(dirs[d]) - offsets[r]);
    let idx = Math.round(rel/SECTOR_ANGLE - 0.5);
    idx = ((idx%SECTORS)+SECTORS)%SECTORS;
    const num = RINGS[r].numbers[idx];
    if(mode!=='verde' && highlighted.has(num)) score += 1;
    if(mode!=='oro' && drawHighlighted.has(num)) score += (mode==='entrambi' ? 0.5 : 1);
  }
  return score;
}
function findBestAlignments(){
  const mode = document.getElementById('alignMode').value;
  if(!highlighted.size && !drawHighlighted.size){
    showNotification('⚠️ Evidenzia prima almeno un numero');
    return;
  }
  document.getElementById('alignmentResult').innerHTML = '<span>⏳ Ricerca in corso...</span>';
  setTimeout(()=>{
    const results = [];
    let minScore = -1;
    for(let o0=0;o0<360;o0+=SECTOR_ANGLE)
      for(let o1=0;o1<360;o1+=SECTOR_ANGLE)
        for(let o2=0;o2<360;o2+=SECTOR_ANGLE){
          const t=[o0+REST_OFFSET,o1+REST_OFFSET,o2+REST_OFFSET];
          const s = alignmentScoreFor(t, mode);
          if(results.length<5){
            results.push({offsets:t, score:s});
            if(results.length===5){ results.sort((a,b)=>b.score-a.score); minScore=results[4].score; }
          } else if(s>minScore){
            results[4]={offsets:t, score:s};
            results.sort((a,b)=>b.score-a.score);
            minScore=results[4].score;
          }
        }
    results.sort((a,b)=>b.score-a.score);
    renderAlignTopList(results, mode);
  }, 30);
}
function renderAlignTopList(results, mode){
  const out = document.getElementById('alignmentResult');
  const box = document.getElementById('alignTopList');
  if(!results[0] || results[0].score<=0){
    out.innerHTML = '<span style="color:#e0a0a0;">Nessun allineamento possibile con i numeri attuali</span>';
    box.innerHTML = '';
    return;
  }
  out.innerHTML = `<span>Trovate <b>${results.length}</b> configurazioni — scegline una</span>`;
  box.innerHTML = results.map((r,i)=>`
    <div style="display:flex;align-items:center;gap:8px;padding:6px 8px;background:rgba(0,0,0,.15);border-radius:6px;margin-top:6px;font-size:12.5px;">
      <span style="color:var(--brass-hi);font-weight:700;">#${i+1}</span>
      <span style="flex:1;color:#c9b98f;">Punteggio <b style="color:#4fae7a;">${r.score.toFixed(1)}</b></span>
      <button class="step" data-idx="${i}" style="flex:0 0 auto;padding:6px 12px;">Applica</button>
    </div>`).join('');
  box.querySelectorAll('button[data-idx]').forEach(btn=>{
    btn.addEventListener('click',()=>{
      const i = parseInt(btn.dataset.idx);
      offset[0]=results[i].offsets[0]; offset[1]=results[i].offsets[1]; offset[2]=results[i].offsets[2];
      renderAll();
      out.innerHTML = `<span><strong>#${i+1} applicata — Punteggio ${results[i].score.toFixed(1)}</strong></span>`;
      renderAlignDetail();
      showNotification(`✨ Allineamento applicato (score ${results[i].score.toFixed(1)})`);
    });
  });
}
function renderAlignDetail(){
  const dirs = compassAngles();
  const nomi = ['Interno','Medio','Esterno'];
  let html = '<div style="margin-top:8px;font-size:12px;">';
  html += '<div style="color:var(--brass-hi);font-weight:600;margin-bottom:4px;">Numeri sui punti cardinali:</div>';
  let found = 0;
  for(const d in dirs) for(let r=0;r<3;r++){
    const v = valueAt(r, dirs[d]);
    if(v===null) continue;
    const isOro = highlighted.has(v), isVerde = drawHighlighted.has(v);
    if(isOro || isVerde){
      const badges = [isOro?'🟡':'', isVerde?'🟢':''].filter(Boolean).join('');
      html += `<div style="padding:3px 6px;background:rgba(79,174,122,.12);border-radius:4px;margin-bottom:3px;">
        <b style="color:var(--gold-fill);">${d}</b> · ${nomi[r]} · <b>${v}</b> ${badges}
      </div>`;
      found++;
    }
  }
  if(!found) html += '<div style="color:#8a7a5c;">Nessun numero evidenziato sui cardinali</div>';
  html += '</div>';
  document.getElementById('alignDetail').innerHTML = html;
}

// ============================================================
// EVIDENZIAZIONI E CHIP
// ============================================================
function toggleHighlight(n){
  if(highlighted.has(n)) highlighted.delete(n); else highlighted.add(n);
  renderChips(); renderAll();
}
function renderChips(){
  const box = document.getElementById('chips');
  box.innerHTML = [...highlighted].sort((a,b)=>a-b).map(n=>`<span class="chip" data-n="${n}">${n} ✕</span>`).join('');
  box.querySelectorAll('.chip').forEach(chip=>{
    chip.addEventListener('click',()=>{ highlighted.delete(parseInt(chip.dataset.n)); renderChips(); renderAll(); });
  });
}
function renderDrawChips(){
  const box = document.getElementById('drawChips');
  box.innerHTML = [...drawHighlighted].sort((a,b)=>a-b).map(n=>`<span class="chip draw-chip" data-n="${n}">${n} ✕</span>`).join('');
  box.querySelectorAll('.chip').forEach(chip=>{
    chip.addEventListener('click',()=>{ drawHighlighted.delete(parseInt(chip.dataset.n)); renderDrawChips(); renderAll(); });
  });
  if(drawHighlighted.size >= 2){
    analizzaCoerenzaEstrazioneUI([...drawHighlighted]);
  } else {
    document.getElementById('coerenzaBox').innerHTML = '';
  }
}
function parseDrawNumbers(text){
  return [...new Set((text.match(/\d{1,2}/g)||[]).map(s=>parseInt(s)).filter(n=>n>=1&&n<=90))];
}
function renderAll(){ renderRing(0);renderRing(1);renderRing(2); }

// ============================================================
// CONFIGURAZIONI, CONDIVISIONE, TEMA
// ============================================================
function saveConfiguration(){
  const nameInput = document.getElementById('configNameInput');
  const name = nameInput.value.trim()||`Config ${new Date().toLocaleString('it-IT')}`;
  const config = {name,offsets:[...offset],highlighted:[...highlighted],drawHighlighted:[...drawHighlighted]};
  const saved = JSON.parse(localStorage.getItem('cerchioConfigs')||'[]');
  saved.push(config);
  localStorage.setItem('cerchioConfigs',JSON.stringify(saved));
  nameInput.value='';
  updateSavedList();
  showNotification(`💾 "${name}" salvata`);
}
function loadConfiguration(i){
  const saved = JSON.parse(localStorage.getItem('cerchioConfigs')||'[]');
  const c = saved[i]; if(!c) return;
  offset[0]=c.offsets[0];offset[1]=c.offsets[1];offset[2]=c.offsets[2];
  highlighted.clear();c.highlighted.forEach(n=>highlighted.add(n));
  drawHighlighted.clear();c.drawHighlighted.forEach(n=>drawHighlighted.add(n));
  renderAll();
  showNotification(`📂 Caricata: ${c.name}`);
}
function deleteConfiguration(i){
  const saved = JSON.parse(localStorage.getItem('cerchioConfigs')||'[]');
  saved.splice(i,1);
  localStorage.setItem('cerchioConfigs',JSON.stringify(saved));
  updateSavedList();
}
function updateSavedList(){
  const list = document.getElementById('savedList');
  const saved = JSON.parse(localStorage.getItem('cerchioConfigs')||'[]');
  if(!saved.length){ list.innerHTML='<div style="text-align:center;color:#8a7a5c;font-size:12px;padding:8px;">Nessuna configurazione</div>'; return; }
  list.innerHTML = saved.map((c,i)=>`<div class="saved-item"><span class="name" onclick="loadConfiguration(${i})">${c.name}</span><span class="del" onclick="deleteConfiguration(${i})">✕</span></div>`).join('');
}
function generateShareLink(){
  const params = new URLSearchParams();
  params.set('o',offset.map(o=>o.toFixed(2)).join(','));
  params.set('h',[...highlighted].join(','));
  params.set('d',[...drawHighlighted].join(','));
  navigator.clipboard.writeText(`${location.origin}${location.pathname}?${params}`).then(()=>showNotification('🔗 Link copiato')).catch(()=>prompt('Copia:',`${location.origin}${location.pathname}?${params}`));
}
function loadFromURL(){
  const params = new URLSearchParams(location.search);
  if(params.has('o')){
    const offs = params.get('o').split(',').map(Number);
    if(offs.length===3){offset[0]=offs[0];offset[1]=offs[1];offset[2]=offs[2];}
  }
  if(params.has('h')) params.get('h').split(',').forEach(n=>{n=parseInt(n);if(n>=1&&n<=90)highlighted.add(n);});
  if(params.has('d')) params.get('d').split(',').forEach(n=>{n=parseInt(n);if(n>=1&&n<=90)drawHighlighted.add(n);});
}
function toggleTheme(){
  const html = document.documentElement;
  const next = html.getAttribute('data-theme')==='light'?'dark':'light';
  html.setAttribute('data-theme',next);
  localStorage.setItem('theme',next);
  document.getElementById('themeToggle').textContent = next==='dark'?'🌙 Tema':'☀️ Tema';
}
function initTheme(){
  const saved = localStorage.getItem('theme');
  if(saved){ document.documentElement.setAttribute('data-theme',saved); document.getElementById('themeToggle').textContent = saved==='dark'?'🌙 Tema':'☀️ Tema'; }
}
function doFind(){
  const input = document.getElementById('finderInput');
  const n = parseInt(input.value);
  if(!n||n<1||n>90) return;
  highlighted.add(n);
  renderChips(); renderAll();
  input.select();
}

// ============================================================
// EVENT LISTENERS
// ============================================================
document.querySelectorAll('button.step[data-ring]').forEach(btn=>{
  btn.addEventListener('click',()=>stepRing(parseInt(btn.dataset.ring),parseInt(btn.dataset.dir)));
});
document.getElementById('resetBtn').addEventListener('click',()=>{offset[0]=offset[1]=offset[2]=REST_OFFSET;renderAll();});
document.getElementById('clearHighlights').addEventListener('click',()=>{highlighted.clear();renderChips();renderAll();});
document.getElementById('finderGo').addEventListener('click',doFind);
document.getElementById('finderInput').addEventListener('keydown',e=>{if(e.key==='Enter')doFind();});
document.getElementById('drawGo').addEventListener('click',()=>{
  parseDrawNumbers(document.getElementById('drawInput').value).forEach(n=>drawHighlighted.add(n));
  renderDrawChips();renderAll();
});
document.getElementById('drawClear').addEventListener('click',()=>{
  drawHighlighted.clear();
  document.getElementById('drawInput').value='';
  document.getElementById('coerenzaBox').innerHTML='';
  renderDrawChips();renderAll();
});
document.getElementById('themeToggle').addEventListener('click',toggleTheme);
document.getElementById('shareBtn').addEventListener('click',generateShareLink);
document.getElementById('guideBtn').addEventListener('click',()=>document.getElementById('guideModal').classList.add('open'));
document.getElementById('guideClose').addEventListener('click',()=>document.getElementById('guideModal').classList.remove('open'));
document.getElementById('guideModal').addEventListener('click',e=>{if(e.target.id==='guideModal')e.currentTarget.classList.remove('open');});
document.getElementById('clearAllBtn').addEventListener('click',()=>{
  if(!confirm('Cancellare tutte le evidenziazioni?')) return;
  highlighted.clear();drawHighlighted.clear();
  offset[0]=offset[1]=offset[2]=REST_OFFSET;
  clearEngine();clearHistory();LAST_BACKTEST=[];
  document.getElementById('coerenzaBox').innerHTML='';
  renderChips();renderDrawChips();renderAll();renderNumeriPromettenti();
  showNotification('🗑️ Tutto pulito');
});
document.getElementById('importDemoBtn').addEventListener('click',()=>{
  [7,23,45,68,81,12,34,56,72,90].forEach(n=>drawHighlighted.add(n));
  renderDrawChips();renderAll();
  showNotification('📥 Demo caricata');
});
// Statistiche
document.getElementById('statsRun').addEventListener('click',mostraStatisticheTop10);
document.getElementById('statsTop10').addEventListener('click',mostraStatisticheTop10);
document.getElementById('statsPattern').addEventListener('click',mostraStatistichePattern);
// Archivi - SISTEMA UPLOAD LOCALE
document.getElementById('btnLoadLocal').addEventListener('click',()=>{
  document.getElementById('fileInput').click();
});
document.getElementById('fileInput').addEventListener('change',async(e)=>{
  const file = e.target.files[0];
  if(!file) return;
  if(!file.name.toLowerCase().endsWith('.zip')){
    showNotification('⚠️ Seleziona un file ZIP');
    return;
  }
  await caricaArchivioLocale(file);
  e.target.value = ''; // Reset per permettere ricaricamento stesso file
});
document.getElementById('btnLoadCache').addEventListener('click',async()=>{
  const dati = await caricaCache();
  if(dati){ updateArchiveUI(); setupBtDateRange(); showNotification('💾 Cache caricata'); }
});
document.getElementById('btnValida').addEventListener('click',validaDati);
document.getElementById('btnClearCache').addEventListener('click',async()=>{
  if(confirm('Cancellare l\'archivio delle estrazioni?')) await cancellaCache();
});
document.getElementById('btnUseLast1').addEventListener('click',()=>portaEstrazioni(1));
document.getElementById('btnUseLast3').addEventListener('click',()=>portaEstrazioni(3));
// Verifica a Ritroso
document.getElementById('btRun').addEventListener('click',runBacktest);
// Numeri Promettenti
document.getElementById('esitiRefresh').addEventListener('click',renderNumeriPromettenti);
// Motore L1
document.getElementById('engineRun').addEventListener('click',runEngine);
document.getElementById('heatToggle').addEventListener('click',toggleHeat);
document.getElementById('tourBtn').addEventListener('click',startTour);
document.getElementById('engineClear').addEventListener('click',clearEngine);
// Storico L2
document.getElementById('histLoad').addEventListener('click',loadHistoryFromTextarea);
document.getElementById('histDemo').addEventListener('click',loadHistoryDemo);
document.getElementById('histClear').addEventListener('click',clearHistory);
document.getElementById('convRun').addEventListener('click',runConvergence);
// Carica ruote da archivio
document.getElementById('btnCaricaRuoteConv').addEventListener('click',caricaRuoteDaArchivioPerConvergenze);
// Allineamenti e configurazioni
document.getElementById('findAlignmentBtn').addEventListener('click',findBestAlignments);
document.getElementById('saveConfigBtn').addEventListener('click',saveConfiguration);

// ============================================================
// INIZIALIZZAZIONE
// ============================================================
buildStatic();
RINGS.forEach((r,i)=>buildRing(r,i));
buildCompass(ringGroups[2]);
overlayG = el('g',{id:'figureOverlay'});
svg.appendChild(overlayG);
renderAll();
renderChips();
renderDrawChips();
updateSavedList();
updateRuotaFilter();
popolaSelectRuote();
popolaBtRuote();
popolaConvArchivioRuote();
initTheme();
loadFromURL();
updateArchiveUI();
setupBtDateRange();
if(cacheEsiste()){
  setTimeout(async()=>{
    const dati = await caricaCache();
    if(dati){ updateArchiveUI(); setupBtDateRange(); }
  }, 300);
}
</script>
</body>
</html>

HTML:
<!DOCTYPE html>
<html lang="it" data-theme="dark">
<head>
<meta charset="UTF-8">
<title>Guida all'Utilizzo — Cerchio di Spilimbergo v2.2</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
  @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;600;700&family=Inter:wght@400;500;600&display=swap');

  :root[data-theme="dark"]{
    --brass:#b4842c; --brass-hi:#e0b04a;
    --paper-1:#f4ecd8; --ink:#2c1e12; --ink-soft:#5a4630;
    --garnet:#8c2a2a; --accent:#0f6e6e; --gold-fill:#e0b04a;
    --panel-bg:linear-gradient(180deg,#2b1f12,#201709);
    --body-bg:radial-gradient(ellipse at 50% -10%, #2c2013 0%, #1a130b 55%);
    --input-bg:#171009; --line:#4a3a20; --muted:#b3a37e; --muted2:#c9b98f;
    --ok:#4fae7a; --warn:#e0b04a; --err:#e0a0a0;
  }
  :root[data-theme="light"]{
    --brass:#8b6914; --brass-hi:#a67c1a;
    --paper-1:#2c1e12; --ink:#f4ecd8; --ink-soft:#8a7a5c;
    --garnet:#b23a3a; --accent:#1a8f8f; --gold-fill:#d4a017;
    --panel-bg:linear-gradient(180deg,#f5edd8,#e8dcc4);
    --body-bg:radial-gradient(ellipse at 50% -10%, #f5edd8 0%, #d4c5a0 55%);
    --input-bg:#faf6eb; --line:#b9a877; --muted:#6b5b3e; --muted2:#5a4630;
    --ok:#1a6b3c; --warn:#8b6914; --err:#b23a3a;
  }
  *{box-sizing:border-box;}
  html{scroll-behavior:smooth;}
  body{margin:0;min-height:100%;background:var(--body-bg);color:var(--paper-1);
    font-family:'Inter',sans-serif;transition:background .4s,color .4s;}
  .wrap{max-width:1200px;margin:0 auto;padding:28px 16px 80px;}
  header.top{text-align:center;padding:10px 0 18px;border-bottom:1px solid var(--line);margin-bottom:22px;}
  h1{font-family:'Cormorant Garamond',serif;font-weight:700;font-size:clamp(28px,4vw,44px);margin:0 0 4px;color:var(--brass-hi);}
  .subtitle{font-family:'Cormorant Garamond',serif;font-style:italic;color:var(--muted2);font-size:16px;margin:0 0 14px;}
  .toolbar{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;}
  .toolbar button{background:var(--panel-bg);border:1px solid var(--brass);color:var(--paper-1);
    font-family:'Inter',sans-serif;font-size:13px;padding:8px 14px;border-radius:8px;cursor:pointer;transition:all .2s;}
  .toolbar button:hover{border-color:var(--brass-hi);transform:translateY(-1px);}
  .searchbox{max-width:420px;margin:12px auto 0;display:flex;gap:8px;}
  .searchbox input{flex:1;background:var(--input-bg);border:1px solid var(--brass);color:var(--paper-1);
    border-radius:8px;padding:9px 12px;font-family:'Inter',sans-serif;font-size:13px;}
  .search-count{text-align:center;font-size:11px;color:var(--muted);min-height:16px;margin-top:4px;}

  .layout{display:grid;grid-template-columns:250px 1fr;gap:26px;align-items:start;}
  @media (max-width:900px){.layout{grid-template-columns:1fr;}}
  nav.toc{position:sticky;top:14px;background:var(--panel-bg);border:1px solid var(--line);border-radius:12px;padding:14px;}
  @media (max-width:900px){nav.toc{position:static;}}
  nav.toc h3{margin:0 0 10px;font-family:'Cormorant Garamond',serif;color:var(--brass-hi);font-size:17px;}
  nav.toc a{display:block;color:var(--muted2);text-decoration:none;font-size:12.5px;padding:5px 8px;border-radius:6px;line-height:1.4;}
  nav.toc a:hover{background:rgba(224,176,74,.15);color:var(--brass-hi);}
  nav.toc a.hidden{display:none;}

  section.card{background:var(--panel-bg);border:1px solid var(--line);border-radius:12px;padding:16px 18px;margin-bottom:16px;scroll-margin-top:14px;}
  section.card.match{border-color:var(--ok);box-shadow:0 0 0 1px var(--ok);}
  h2{font-family:'Cormorant Garamond',serif;color:var(--brass-hi);font-size:22px;margin:0 0 4px;display:flex;align-items:center;gap:10px;}
  h2 .n{font-size:13px;font-family:'Inter',sans-serif;background:var(--brass);color:#1a1206;border-radius:8px;padding:2px 8px;font-weight:700;}
  h4{font-family:'Cormorant Garamond',serif;color:var(--brass-hi);font-size:16px;margin:16px 0 6px;}
  p,li{font-size:13.5px;line-height:1.65;}
  ul,ol{padding-left:22px;margin:6px 0;}
  .uibtn{display:inline-block;background:#332512;border:1px solid var(--brass);color:var(--paper-1);
    font-size:12px;padding:2px 9px;border-radius:7px;white-space:nowrap;}
  :root[data-theme="light"] .uibtn{background:#e8dcc4;}
  .tip,.warn,.note{border-radius:8px;padding:10px 12px;font-size:12.5px;line-height:1.6;margin:10px 0;}
  .tip{background:rgba(79,174,122,.12);border:1px solid var(--ok);}
  .warn{background:rgba(224,176,74,.12);border:1px solid var(--warn);}
  .note{background:rgba(15,110,110,.12);border:1px solid var(--accent);}
  table{width:100%;border-collapse:collapse;margin:8px 0;}
  th{font-weight:600;font-size:11.5px;color:var(--muted2);text-align:left;padding:6px 8px;border-bottom:1px solid var(--line);}
  td{font-size:12.5px;padding:6px 8px;border-bottom:1px solid var(--line);vertical-align:top;line-height:1.5;}
  td b{color:var(--brass-hi);}
  .flow{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:10px 0;}
  .flow .stepf{background:var(--panel-bg);border:1px solid var(--brass);border-radius:10px;padding:6px 12px;font-size:12px;}
  .flow .arrow{color:var(--brass-hi);font-weight:700;}
  .gloss{display:grid;grid-template-columns:1fr 1fr;gap:8px;}
  @media (max-width:700px){.gloss{grid-template-columns:1fr;}}
  .gloss .item{background:rgba(0,0,0,.12);border-radius:8px;padding:8px 10px;font-size:12.5px;line-height:1.55;}
  :root[data-theme="light"] .gloss .item{background:rgba(0,0,0,.05);}
  .gloss .item b{color:var(--gold-fill);}
  kbd{background:var(--input-bg);border:1px solid var(--line);border-radius:4px;padding:1px 6px;font-size:11px;font-family:'Courier New',monospace;}
  #topBtn{position:fixed;right:18px;bottom:18px;width:44px;height:44px;border-radius:50%;border:1px solid var(--brass);
    background:var(--panel-bg);color:var(--brass-hi);font-size:18px;cursor:pointer;opacity:0;pointer-events:none;transition:opacity .3s;z-index:99;}
  #topBtn.show{opacity:1;pointer-events:auto;}
  footer{text-align:center;font-size:11.5px;color:var(--muted);margin-top:26px;line-height:1.7;}
  @media print{
    nav.toc,.toolbar,.searchbox,#topBtn{display:none !important;}
    .layout{grid-template-columns:1fr;}
    section.card{break-inside:avoid;border:1px solid #999;}
    body{background:#fff;color:#111;}
  }
</style>
</head>
<body>
<div class="wrap">

<header class="top">
  <h1>Guida all'Utilizzo</h1>
  <p class="subtitle">Cerchio di Spilimbergo — Suite Completa v2.2 · Archivio ZIP · Ciclometria · Motore Previsivo · Backtest</p>
  <div class="toolbar">
    <button id="themeToggle">🌙 Tema</button>
    <button id="openAll">📖 Apri tutto</button>
    <button id="closeAll">📕 Comprimi</button>
    <button id="printBtn">🖨️ Stampa</button>
  </div>
  <div class="searchbox"><input id="searchInput" type="text" placeholder="🔎 Cerca nella guida (es. convergenze, ZIP, opposti...)"></div>
  <div class="search-count" id="searchCount"></div>
</header>

<div class="layout">

<nav class="toc" id="toc">
  <h3>Indice</h3>
  <a href="#s1">1. Primi passi & interfaccia</a>
  <a href="#s2">2. Archivio: caricamento ZIP</a>
  <a href="#s3">3. Il Cerchio e le ghiere</a>
  <a href="#s4">4. Lettura ai punti cardinali</a>
  <a href="#s5">5. Statistiche & Pattern</a>
  <a href="#s6">6. Estrazione singola & Coerenza</a>
  <a href="#s7">7. Motore Previsivo L1</a>
  <a href="#s8">8. Storico & Convergenze L2</a>
  <a href="#s9">9. Verifica a Ritroso (Backtest)</a>
  <a href="#s10">10. Numeri Promettenti</a>
  <a href="#s11">11. Distanze & Figure</a>
  <a href="#s12">12. Allineamenti potenziati</a>
  <a href="#s13">13. Configurazioni & condivisione</a>
  <a href="#s14">14. Workflow consigliato</a>
  <a href="#s15">15. Glossario ciclometrico</a>
  <a href="#s16">16. Risoluzione problemi (FAQ)</a>
  <a href="#s17">17. Avvertenze finali</a>
</nav>

<main>

<section class="card" id="s1">
  <h2><span class="n">1</span> Primi passi & interfaccia</h2>
  <p>La Suite è un file HTML autonomo: basta aprirlo con un browser moderno (Chrome, Edge, Firefox). Non serve installazione né connessione stabile, se non per scaricare l'archivio delle estrazioni.</p>
  <h4>Barra superiore</h4>
  <ul>
    <li><span class="uibtn">🌙 Tema</span> — alterna tema scuro/chiaro (preferenza salvata).</li>
    <li><span class="uibtn">🔗 Condividi</span> — copia un link con posizione delle ghiere ed evidenziazioni.</li>
    <li><span class="uibtn">📥 Demo</span> — carica 10 numeri di esempio (verdi) per provare subito tutto.</li>
    <li><span class="uibtn">🗑️ Pulisci Tutto</span> — azzera cerchio, motori, storico e backtest.</li>
  </ul>
  <h4>Layout</h4>
  <p>A sinistra il <b>Cerchio</b> interattivo (SVG), a destra il <b>Pannello</b> scorrevole con tutti i moduli di analisi. Su schermi stretti il pannello scende sotto il cerchio.</p>
  <div class="note">💡 I numeri <b style="color:var(--gold-fill)">ORO</b> sono le tue evidenziazioni manuali/previsioni; i numeri <b style="color:var(--ok)">VERDI</b> sono le estrazioni caricate (archivio o manuale).</div>
</section>

<section class="card" id="s2">
  <h2><span class="n">2</span> Archivio: caricamento ZIP</h2>
  <p>L'archivio storico è la base per Statistiche, Backtest, Convergenze e caricamenti rapidi. Il sito sorgente non consente il download diretto dal browser (CORS), perciò il caricamento è <b>manuale e locale</b>.</p>
  <ol>
    <li>Scarica una volta il file: <kbd>storico.zip</kbd> dal link indicato nel riquadro istruzioni del pannello Archivi.</li>
    <li>Premi <span class="uibtn">📂 Carica ZIP Locale</span> e seleziona il file.</li>
    <li>Il sistema decomprime il ZIP, legge <kbd>storico.txt</kbd>, valida ogni riga e crea la <b>cache su IndexedDB</b>.</li>
    <li>Nelle sessioni successive la cache si ricarica da sola; in alternativa usa <span class="uibtn">💾 Carica Cache</span>.</li>
  </ol>
  <h4>Codici ruota nel file</h4>
  <table>
    <tr><th>Codice</th><th>Ruota</th><th>Codice</th><th>Ruota</th></tr>
    <tr><td><b>BA</b> Bari</td><td><b>CA</b> Cagliari</td><td><b>FI</b> Firenze</td><td><b>GE</b> Genova</td></tr>
    <tr><td><b>MI</b> Milano</td><td><b>NA</b> Napoli</td><td><b>PA</b> Palermo</td><td><b>RM</b> Roma</td></tr>
    <tr><td><b>RN</b> Nazionale</td><td><b>TO</b> Torino</td><td><b>VE</b> Venezia</td><td></td></tr>
  </table>
  <div class="note">ℹ️ Nel file sorgente la Nazionale è <b>RN</b> e Roma è <b>RM</b>: la Suite li riconosce e li mappa automaticamente.</div>
  <h4>Strumenti archivio</h4>
  <ul>
    <li><span class="uibtn">🔍 Valida</span> — report per ruota: numero righe, prima/ultima data, epoche Venus e 4 estrazioni.</li>
    <li><b>Epoca storica</b> — filtra i dati usati sul Cerchio (Completo / Venus 2005+ / 4 estrazioni 2023+).</li>
    <li><span class="uibtn">🎯 Ultima estrazione</span> / <span class="uibtn">🎯 Ultime 3</span> — portano i numeri (verdi) della ruota scelta direttamente sul Cerchio.</li>
    <li><span class="uibtn">🗑️ Cancella</span> — elimina la cache permanente.</li>
  </ul>
  <div class="tip">✅ Quando esce il nuovo concorso: riscarica <kbd>storico.zip</kbd> e ricaricalo con <span class="uibtn">📂 Carica ZIP Locale</span>. I duplicati data+ruota vengono scartati automaticamente.</div>
</section>

<section class="card" id="s3">
  <h2><span class="n">3</span> Il Cerchio e le ghiere</h2>
  <p>Il Cerchio di Spilimbergo dispone i 90 numeri del Lotto su <b>tre ghiere concentriche</b> da 30 numeri ciascuna, generate con passo ciclometrico 23. Ogni ghiera ha 32 settori: 30 numerati + 2 settori di gap (visibili come tacche scure).</p>
  <ul>
    <li><b>Rotazione libera</b>: trascina una ghiera col mouse/touch; al rilascio scatta allo scatto più vicino (snap).</li>
    <li><b>Rotazione a passi</b>: frecce <span class="uibtn">↺</span> <span class="uibtn">↻</span> nelle card Anello interno/medio/esterno.</li>
    <li><b>Evidenzia</b>: click/tap su un numero lo accende in ORO (riclick lo spegne).</li>
    <li><span class="uibtn">Riporta le ghiere a riposo</span> — ripristina la posizione neutra.</li>
  </ul>
  <div class="tip">✅ La bussola N/E/S/O è solidale all'anello esterno: ruotandolo, ruotano anche i punti di lettura.</div>
</section>

<section class="card" id="s4">
  <h2><span class="n">4</span> Lettura ai punti cardinali</h2>
  <p>La tabella mostra in tempo reale i numeri che cadono sotto <b>N, S, E, O</b> per ciascuna ghiera, in base alle rotazioni attuali. È la "lettura" classica del cerchio: le terne/quaterne cardinali sono considerate terne di studio.</p>
  <p>Se una lancetta cade su un settore di gap, la cella mostra <i>—</i>.</p>
</section>

<section class="card" id="s5">
  <h2><span class="n">5</span> Statistiche & Pattern</h2>
  <ol>
    <li>Seleziona la ruota nel menu e premi <span class="uibtn">Analizza</span> o <span class="uibtn">Top 10 Frequenti</span>.</li>
    <li>Vedi i 10 numeri più usciti nelle ultime 100 estrazioni, con frequenza e ritardo. <b>Click su un numero</b> → evidenziato in ORO.</li>
    <li><span class="uibtn">Pattern Coppie</span> mostra le 15 coppie e 10 triple più ricorrenti (ultime 200 estrazioni). Click → tutti i numeri della coppia/tripla finiscono sul Cerchio.</li>
  </ol>
  <div class="note">ℹ️ Richiede l'archivio caricato (sezione 2).</div>
</section>

<section class="card" id="s6">
  <h2><span class="n">6</span> Estrazione singola & Coerenza</h2>
  <ol>
    <li>Incolla i 5 numeri nel campo <b>Estrazione da analizzare</b> e premi <span class="uibtn">Evidenzia</span> (diventano VERDI).</li>
    <li>Sotto i chip appare il riquadro <b>Coerenza geometrica</b>: punteggio e elenco delle figure ciclometriche contenute nell'estrazione (opposti, trigoni, sesti, somme cifre).</li>
    <li>I chip verdi permettono di rimuovere un numero alla volta; <span class="uibtn">Pulisci</span> azzera tutto.</li>
  </ol>
  <div class="tip">✅ Un'estrazione con coerenza alta è "geometricamente parlante": molti studiosi la preferiscono come base per il Motore L1.</div>
</section>

<section class="card" id="s7">
  <h2><span class="n">7</span> Motore Previsivo L1</h2>
  <p>Parte dai numeri VERDI (o ORO se non ci sono verdi, minimo 2) e calcola i "numeri dovuti" per legge ciclometrica: opposti, trigoni, sesti, somme cifre ridotte, distanze, somme e equidistanti di coppia.</p>
  <ul>
    <li><span class="uibtn">⚡ Genera figure e previsione</span> — disegna le figure sul Cerchio e propone Amata / Ambo / Terno / Cinquina.</li>
    <li><span class="uibtn">🔥 Calore</span> — mappa termica: i candidati migliori risaltano sul Cerchio.</li>
    <li><span class="uibtn">🎬 Tour</span> — passerella animata delle figure rilevate.</li>
    <li>Click su un candidato nell'elenco → evidenziato in ORO.</li>
    <li><span class="uibtn">🧹</span> — pulisce figure e candidati.</li>
  </ul>
</section>

<section class="card" id="s8">
  <h2><span class="n">8</span> Storico & Convergenze L2</h2>
  <p>Le convergenze cercano numeri che risultano "dovuti" <b>da più estrazioni contemporaneamente</b>.</p>
  <ol>
    <li><b>Da archivio</b>: scegli ruota (o tutte) e N estrazioni recenti, poi <span class="uibtn">📥 Carica estrazioni nelle Convergenze</span>. Il calcolo parte in automatico.</li>
    <li><b>A mano</b>: incolla righe tipo <kbd>Bari 7 23 45 68 81</kbd> e premi <span class="uibtn">📥 Carica</span>.</li>
    <li>Premi <span class="uibtn">🔀 Convergenze</span>: i numeri con badge "in X/Y" sono candidati da X estrazioni su Y.</li>
    <li><span class="uibtn">🎯 Evidenzia i convergenti</span> porta i top 8 in ORO sul Cerchio.</li>
  </ol>
</section>

<section class="card" id="s9">
  <h2><span class="n">9</span> Verifica a Ritroso (Backtest)</h2>
  <p>Simula una previsione nel passato e misura quanti numeri avrebbe centrato.</p>
  <ol>
    <li>Scegli <b>ruota</b>, <b>data</b> col calendario, numero di <b>estrazioni base</b> e <b>top candidati</b>.</li>
    <li><span class="uibtn">🔍 Verifica a ritroso</span>: il motore calcola ritardatari + figure + frequenza fino a quella data e confronta con l'estrazione <b>reale successiva</b>.</li>
    <li>I centri sono marcati in oro; vedi il % di match.</li>
    <li>Se la data scelta non ha concorso, usa i link <i>vai alla data precedente/successiva</i>.</li>
    <li><span class="uibtn">🎯 Porta previsti sul Cerchio</span> / <span class="uibtn">📗 Porta estrazione reale</span> per lo studio visivo.</li>
  </ol>
  <div class="warn">⚠️ Il backtest misura la coerenza interna del metodo, non la sua capacità di prevedere il futuro (vedi sezione 17).</div>
</section>

<section class="card" id="s10">
  <h2><span class="n">10</span> Numeri Promettenti</h2>
  <p>È il <b>sintetizzatore finale</b>: fonde Motore L1, Convergenze e Backtest con pesi intelligenti.</p>
  <ul>
    <li>Ogni fonte contribuisce col proprio score; le convergenze pesano di più se ricorrono in molte estrazioni; il backtest pesa di più per i primi posti.</li>
    <li><b>Bonus coerenza +25%</b> per ogni fonte aggiuntiva che suggerisce lo stesso numero.</li>
    <li>Il pallino <b style="color:#e0552c">ARANCIO</b> è il top assoluto; i puntini colorati sotto ogni numero indicano quali motori lo hanno proposto.</li>
    <li>Click su un pallino → numero in ORO sul Cerchio.</li>
  </ul>
  <div class="flow">
    <span class="stepf">⚡ Motore</span><span class="arrow">+</span>
    <span class="stepf">🔀 Convergenze</span><span class="arrow">+</span>
    <span class="stepf">🕰️ Backtest</span><span class="arrow">→</span>
    <span class="stepf">🎯 Numeri Promettenti</span>
  </div>
</section>

<section class="card" id="s11">
  <h2><span class="n">11</span> Distanze & Figure</h2>
  <p>Con almeno 2 numeri ORO, il pannello elenca per ogni coppia la <b>distanza ciclometrica Δ</b> e il <b>complemento C</b> (90−Δ). Le figure riconosciute:</p>
  <div class="gloss">
    <div class="item"><b>Opposti 180°</b> — coppia con Δ45: diametralmente opposti.</div>
    <div class="item"><b>Triangolo 120°</b> — terna con passi 30 (n, n+30, n+60).</div>
    <div class="item"><b>Terzina 60°</b> — terna con passi 15 (n, n+15, n+30).</div>
    <div class="item"><b>Quadrato 90°</b> — quaterna con passi 22/23 (n, n+22, n+45, n+67).</div>
  </div>
  <p>Le linee tratteggiate oro sul Cerchio collegano le coppie di opposti.</p>
</section>

<section class="card" id="s12">
  <h2><span class="n">12</span> Allineamenti potenziati</h2>
  <p>Cerca le rotazioni delle tre ghiere che portano il maggior numero di tuoi numeri (ORO/VERDI) sui punti cardinali.</p>
  <ol>
    <li>Scegli il modo: <b>entrambi</b>, solo ORO o solo VERDI.</li>
    <li><span class="uibtn">🔍 Trova migliori configurazioni</span> (esamina 32.768 combinazioni).</li>
    <li>Le top 5 configurazioni sono elencate col punteggio: <span class="uibtn">Applica</span> per ruotare il Cerchio.</li>
    <li>Il dettaglio mostra quali numeri cadono su N/S/E/O dopo l'applicazione.</li>
  </ol>
</section>

<section class="card" id="s13">
  <h2><span class="n">13</span> Configurazioni & condivisione</h2>
  <ul>
    <li><b>💾 Configurazioni</b>: dai un nome e salva lo stato (rotazioni + evidenziazioni). Click sul nome per ricaricare, ✕ per eliminare.</li>
    <li><b>🔗 Condividi</b>: genera un URL con parametri <kbd>?o=…&h=…&d=…</kbd> che riproduce cerchio ed evidenziazioni su qualsiasi dispositivo.</li>
    <li><b>Scova numero</b>: digita 1–90 e <span class="uibtn">Trova</span> per evidenziarlo al volo.</li>
  </ul>
</section>

<section class="card" id="s14">
  <h2><span class="n">14</span> Workflow consigliato</h2>
  <div class="flow">
    <span class="stepf">1️⃣ Carica/aggiorna ZIP</span><span class="arrow">→</span>
    <span class="stepf">2️⃣  Ultima estrazione</span><span class="arrow">→</span>
    <span class="stepf">3️⃣ Leggi Coerenza</span><span class="arrow">→</span>
    <span class="stepf">4️⃣ ⚡ Motore L1</span><span class="arrow">→</span>
    <span class="stepf">5️⃣ 🔀 Convergenze (ultime 10)</span><span class="arrow">→</span>
    <span class="stepf">6️⃣ ️ Backtest di controllo</span><span class="arrow">→</span>
    <span class="stepf">7️⃣  Numeri Promettenti</span><span class="arrow">→</span>
    <span class="stepf">8️⃣  Allineamenti & lettura cardinali</span>
  </div>
  <ol>
    <li>Carica l'archivio (o la cache) e porta sul Cerchio l'ultima estrazione della ruota di studio.</li>
    <li>Osserva la coerenza geometrica: ti dice quanto l'estrazione è "figura-dipendente".</li>
    <li>Lancia il Motore L1 e attiva 🔥 Calore per vedere i punti caldi.</li>
    <li>Carica le ultime 10 estrazioni della ruota nelle Convergenze e avviale.</li>
    <li>Fai un backtest su una data di 2–3 settimane fa per saggiare il metodo.</li>
    <li>Aggiorna i <b>Numeri Promettenti</b> e studia i top 3–5 sul Cerchio.</li>
    <li>Usa gli Allineamenti per posizionarli sui cardinali e leggi la tabella N/S/E/O.</li>
    <li>Salva la configurazione o condividila.</li>
  </ol>
</section>

<section class="card" id="s15">
  <h2><span class="n">15</span> Glossario ciclometrico</h2>
  <div class="gloss">
    <div class="item"><b>Distanza ciclometrica Δ</b> — differenza minima tra due numeri su 90 (max 45).</div>
    <div class="item"><b>Complemento C</b> — 90 meno la differenza semplice.</div>
    <div class="item"><b>Opposti</b> — numeri a Δ45 (180° sul cerchio).</div>
    <div class="item"><b>Trigono</b> — numeri a Δ30 o Δ60 (120°).</div>
    <div class="item"><b>Sesto</b> — numeri a Δ15 o Δ75 (60°).</div>
    <div class="item"><b>Quadrato</b> — quaterna a passi 22/23 (90°).</div>
    <div class="item"><b>Somma cifre ridotta</b> — es. 47→4+7=11→2; lega 47 e 2.</div>
    <div class="item"><b>Ritardo</b> — concorsi trascorsi dall'ultima uscita.</div>
    <div class="item"><b>Frequenza</b> — uscite in una finestra di N concorsi.</div>
    <div class="item"><b>Ambo/Terno/Cinquina</b> — 2/3/5 numeri giocati insieme.</div>
  </div>
</section>

<section class="card" id="s16">
  <h2><span class="n">16</span> Risoluzione problemi (FAQ)</h2>
  <table>
    <tr><th style="width:38%;">Problema</th><th>Soluzione</th></tr>
    <tr><td><b>"Failed to fetch"</b> col download diretto</td><td>Normale: il sito blocca il CORS. Usa <span class="uibtn">📂 Carica ZIP Locale</span> col file scaricato a mano (sezione 2).</td></tr>
    <tr><td>"storico.txt non trovato nel ZIP"</td><td>Verifica di aver scaricato il ZIP ufficiale integro; il file interno deve chiamarsi esattamente <kbd>storico.txt</kbd>.</td></tr>
    <tr><td>"Nessuna cache" dopo il caricamento</td><td>Il browser ha negato lo storage: controlla permessi sito / navigazione privata. I dati restano comunque per la sessione.</td></tr>
    <tr><td>Righe scartate nel log</td><td>Il validatore ignora duplicati, numeri fuori 1–90 o codici ruota ignoti: è un comportamento di sicurezza.</td></tr>
    <tr><td>Il Cerchio non risponde al trascinamento</td><td>Trascina sulla fascia di un anello (area tra i numeri), non sul centro. Su touch, usa un dito singolo.</td></tr>
    <tr><td>Numeri Promettenti vuoti</td><td>Devi aver eseguito almeno un motore (⚡, 🔀 o 🕰️) prima di premere ↻ Aggiorna.</td></tr>
    <tr><td>Voglio ripartire da zero</td><td><span class="uibtn">🗑️ Pulisci Tutto</span> in alto; per l'archivio, <span class="uibtn">🗑️ Cancella</span> nel pannello Archivi.</td></tr>
  </table>
</section>

<section class="card" id="s17">
  <h2><span class="n">17</span> Avvertenze finali</h2>
  <div class="warn">⚠️ Il Cerchio di Spilimbergo e la ciclometria sono metodi della <b>tradizione lottologica privi di validità scientifica</b>. Ogni estrazione del Lotto è un evento casuale indipendente: nessun metodo può aumentare le probabilità reali di vincita.</div>
  <p>Usa la Suite come strumento di <b>studio storico-statistico e intrattenimento</b>. Gioca solo ciò che puoi permetterti di perdere; il gioco è vietato ai minori e può causare dipendenza patologica.</p>
</section>

</main>
</div>

<footer>
  Guida generata per la Suite "Cerchio di Spilimbergo v2.2" · Funziona offline · Stampa con 🖨️ per un manuale cartaceo.<br>
  Gioca responsabilmente.
</footer>
</div>

<button id="topBtn" title="Torna su">↑</button>

<script>
// Tema
const root = document.documentElement;
function setTheme(t){ root.setAttribute('data-theme',t); localStorage.setItem('guideTheme',t);
  document.getElementById('themeToggle').textContent = t==='dark'?'🌙 Tema':'☀️ Tema'; }
setTheme(localStorage.getItem('guideTheme')||'dark');
document.getElementById('themeToggle').onclick = ()=> setTheme(root.getAttribute('data-theme')==='dark'?'light':'dark');

// Apri/chiudi sezioni (le sezioni sono sempre visibili; questi pulsanti gestiscono un effetto "focus")
document.getElementById('openAll').onclick = ()=> document.querySelectorAll('section.card').forEach(s=>s.style.display='');
document.getElementById('closeAll').onclick = ()=> window.scrollTo({top:0,behavior:'smooth'});

// Stampa
document.getElementById('printBtn').onclick = ()=> window.print();
window.addEventListener('beforeprint', ()=> document.querySelectorAll('section.card').forEach(s=>s.style.display=''));

// Ricerca interna
const input = document.getElementById('searchInput');
const count = document.getElementById('searchCount');
const sections = [...document.querySelectorAll('section.card')];
const tocLinks = [...document.querySelectorAll('nav.toc a')];
input.addEventListener('input', ()=>{
  const q = input.value.trim().toLowerCase();
  if(!q){ sections.forEach(s=>s.classList.remove('match')); tocLinks.forEach(a=>a.classList.remove('hidden')); count.textContent=''; return; }
  let hits = 0;
  sections.forEach(s=>{
    const ok = s.textContent.toLowerCase().includes(q);
    s.classList.toggle('match', ok);
    if(ok) hits++;
  });
  tocLinks.forEach(a=>{
    const sec = document.querySelector(a.getAttribute('href'));
    a.classList.toggle('hidden', !(sec && sec.textContent.toLowerCase().includes(q)));
  });
  count.textContent = hits ? `${hits} sezion${hits===1?'e':'i'} corrispondenti — evidenziate in verde` : 'Nessun risultato';
});

// Torna su
const topBtn = document.getElementById('topBtn');
window.addEventListener('scroll', ()=> topBtn.classList.toggle('show', window.scrollY>400));
topBtn.onclick = ()=> window.scrollTo({top:0,behavior:'smooth'});
</script>
</body>
</html>
 
ci sono diversi aspetti in alcune logiche implementate che possono risultare interessanti anche da parte di Rubino e i suoi studi e/o indicazioni parziali dalle quali ho tentato di estrarre le logiche e integrarle .

HTML:
<!DOCTYPE html>
<html lang="it" data-theme="dark">
<head>
  <meta charset="UTF-8">
  <title>Cerchio di Spilimbergo — Suite Completa v2.9</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
  <style>
    @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;600;700&family=Inter:wght@400;500;600&display=swap');

    :root[data-theme="dark"] {
      --frame-dark: #241a10;
      --frame-dark2: #1a130b;
      --brass: #b4842c;
      --brass-hi: #e0b04a;
      --paper-1: #f4ecd8;
      --ink: #2c1e12;
      --ink-soft: #5a4630;
      --garnet: #8c2a2a;
      --accent: #0f6e6e;
      --gold-fill: #e0b04a;
      --panel-bg: linear-gradient(180deg, #2b1f12, #201709);
      --body-bg: radial-gradient(ellipse at 50% -10%, #2c2013 0%, #1a130b 55%);
      --input-bg: #171009;
    }

    :root[data-theme="light"] {
      --frame-dark: #e8dcc4;
      --frame-dark2: #d4c5a0;
      --brass: #8b6914;
      --brass-hi: #a67c1a;
      --paper-1: #2c1e12;
      --ink: #f4ecd8;
      --ink-soft: #8a7a5c;
      --garnet: #b23a3a;
      --accent: #1a8f8f;
      --gold-fill: #d4a017;
      --panel-bg: linear-gradient(180deg, #f5edd8, #e8dcc4);
      --body-bg: radial-gradient(ellipse at 50% -10%, #f5edd8 0%, #d4c5a0 55%);
      --input-bg: #faf6eb;
    }

    * { box-sizing: border-box; }

    html, body {
      margin: 0;
      padding: 0;
      min-height: 100%;
      background: var(--body-bg);
      color: var(--paper-1);
      font-family: 'Inter', sans-serif;
      transition: background .4s, color .4s;
    }

    h1 {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 700;
      font-size: clamp(26px, 4vw, 40px);
      margin: 0 0 2px;
      color: var(--brass-hi);
    }

    .subtitle {
      font-family: 'Cormorant Garamond', serif;
      font-style: italic;
      color: #c9b98f;
      font-size: 16px;
      margin: 0 0 20px;
    }

    .wrap {
      max-width: 1320px;
      margin: 0 auto;
      padding: 28px 16px 56px;
    }

    .toolbar {
      display: flex;
      gap: 8px;
      margin-bottom: 16px;
      flex-wrap: wrap;
      justify-content: center;
    }

    .toolbar button {
      background: var(--panel-bg);
      border: 1px solid var(--brass);
      color: var(--paper-1);
      font-family: 'Inter', sans-serif;
      font-size: 13px;
      padding: 8px 14px;
      border-radius: 8px;
      cursor: pointer;
      transition: all .2s;
    }

    .toolbar button:hover {
      border-color: var(--brass-hi);
      transform: translateY(-1px);
    }

    .layout {
      display: grid;
      grid-template-columns: minmax(300px, 560px) 360px;
      gap: 24px;
      align-items: start;
    }

    @media (max-width: 980px) {
      .layout { grid-template-columns: 1fr; }
    }

    .wheel-card {
      position: relative;
      background: var(--panel-bg);
      border: 1px solid #4a3a20;
      border-radius: 16px;
      padding: 10px;
      box-shadow: 0 30px 60px -20px rgba(0,0,0,.6), inset 0 0 30px rgba(0,0,0,.35);
    }

    svg {
      width: 100%;
      height: auto;
      display: block;
      user-select: none;
    }

    #lines-0, #lines-1, #lines-2, #text-0, #text-1, #text-2 {
      transition: transform .5s cubic-bezier(0.34, 1.56, 0.64, 1);
    }

    .num {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 500;
      text-anchor: middle;
      dominant-baseline: middle;
      pointer-events: none;
      cursor: pointer;
      fill: #1a1206;
      transition: fill .3s, font-weight .2s;
    }

    .num.highlighted { fill: var(--ink); font-weight: 800; }
    .num.draw-hit { fill: #1a6b3c; font-weight: 800; }
    .highlight-dot { fill: var(--gold-fill); opacity: 0; pointer-events: none; transition: opacity .3s; }
    .draw-dot { fill: #4fae7a; opacity: 0; pointer-events: none; transition: opacity .3s; }

    .compass-label {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 700;
      font-size: 26px;
      fill: var(--ink);
      text-anchor: middle;
      dominant-baseline: middle;
    }

    .ring-track { fill: transparent; cursor: grab; touch-action: none; }
    .ring-track.dragging { cursor: grabbing; }
    .divider { stroke: var(--ink-soft); stroke-width: 1; opacity: .35; }
    .band { fill: none; stroke: var(--ink-soft); stroke-width: 1.2; opacity: .55; }
    .gap-mark { fill: var(--ink); opacity: .14; }
    .opposite-line { stroke: var(--gold-fill); stroke-width: 2; stroke-dasharray: 4 4; opacity: .6; }

    .panel {
      display: flex;
      flex-direction: column;
      gap: 14px;
      max-height: 88vh;
      overflow-y: auto;
      padding-right: 4px;
    }

    .panel::-webkit-scrollbar { width: 6px; }
    .panel::-webkit-scrollbar-thumb { background: var(--brass); border-radius: 3px; }

    .card {
      background: var(--panel-bg);
      border: 1px solid #4a3a20;
      border-radius: 12px;
      padding: 13px 15px;
    }

    .card h3 {
      margin: 0 0 10px;
      font-family: 'Cormorant Garamond', serif;
      font-weight: 600;
      font-size: 17px;
      color: var(--brass-hi);
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .swatch { width: 11px; height: 11px; border-radius: 50%; display: inline-block; }
    .btn-row { display: flex; gap: 8px; align-items: center; }

    button.step {
      flex: 1;
      background: #332512;
      border: 1px solid var(--brass);
      color: var(--paper-1);
      font-family: 'Inter', sans-serif;
      font-size: 13px;
      padding: 9px 6px;
      border-radius: 8px;
      cursor: pointer;
      transition: all .15s;
    }

    button.step:hover { background: #40300f; border-color: var(--brass-hi); }

    button.step.primary {
      background: var(--brass);
      border-color: var(--brass-hi);
      color: #1a1206;
      font-weight: 600;
    }

    button.step.primary:hover { background: var(--brass-hi); }
    button.step.danger { border-color: var(--garnet); color: #e0a0a0; }
    button.step.danger:hover { background: rgba(140,42,42,.2); }
    button.step:disabled { opacity: .4; cursor: not-allowed; }

    table { width: 100%; border-collapse: collapse; }

    table th {
      font-weight: 500;
      font-size: 12px;
      color: #c9b98f;
      text-align: center;
      padding-bottom: 6px;
      border-bottom: 1px solid #4a3a20;
    }

    table td {
      text-align: center;
      padding: 6px 4px;
      font-family: 'Cormorant Garamond', serif;
      font-weight: 600;
      font-size: 18px;
      color: var(--paper-1);
      border-bottom: 1px solid #332512;
    }

    table td.blank { color: #8a7a5c; opacity: .7; font-style: italic; font-size: 13px; }
    .ring-name-cell { text-align: left; font-family: 'Inter', sans-serif; font-weight: 500; font-size: 12px; color: #c9b98f; }
    .hint { font-size: 12px; line-height: 1.55; color: #b3a37e; padding: 10px 4px 0; }

    textarea, input[type=number], input[type=text], select, input[type=file] {
      background: var(--input-bg);
      border: 1px solid var(--brass);
      color: var(--paper-1);
      font-family: 'Cormorant Garamond', serif;
      font-size: 16px;
      border-radius: 8px;
      padding: 8px;
      width: 100%;
    }

    select { font-family: 'Inter', sans-serif; font-size: 12px; }
    input[type=file] { font-family: 'Inter', sans-serif; font-size: 12px; padding: 6px; }

    input[type=file]::file-selector-button {
      background: var(--brass);
      border: 1px solid var(--brass-hi);
      color: #1a1206;
      font-weight: 600;
      padding: 6px 12px;
      border-radius: 6px;
      cursor: pointer;
      font-size: 12px;
      margin-right: 8px;
    }

    .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }

    .chip {
      background: #332512;
      border: 1px solid var(--gold-fill);
      color: var(--gold-fill);
      font-family: 'Cormorant Garamond', serif;
      font-weight: 600;
      font-size: 14px;
      padding: 2px 9px;
      border-radius: 12px;
      cursor: pointer;
    }

    .chip.draw-chip { border-color: #4fae7a; color: #4fae7a; }

    .archive-info {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 6px 10px;
      background: rgba(0,0,0,.15);
      border-radius: 8px;
      margin-bottom: 8px;
      font-size: 12px;
      color: #c9b98f;
    }

    .status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
    .status-ok { background: #4fae7a; }
    .status-no { background: #e0a0a0; }

    .progress-bar-bg {
      height: 6px;
      background: rgba(0,0,0,.3);
      border-radius: 3px;
      overflow: hidden;
      margin-top: 8px;
      display: none;
    }

    .progress-bar-fill {
      height: 100%;
      background: var(--brass-hi);
      border-radius: 3px;
      transition: width .3s;
      width: 0%;
    }

    .progress-label { font-size: 11px; color: #b3a37e; margin-top: 4px; text-align: center; min-height: 14px; }

    .archive-log {
      max-height: 110px;
      overflow-y: auto;
      margin-top: 8px;
      padding: 6px;
      background: rgba(0,0,0,.2);
      border-radius: 6px;
      font-family: 'Courier New', monospace;
      font-size: 10.5px;
      color: #b3a37e;
      line-height: 1.5;
      display: none;
    }

    .log-ok { color: #4fae7a; }
    .log-warn { color: #e0b04a; }
    .log-err { color: #e0a0a0; }

    .archive-use { margin-top: 10px; padding-top: 10px; border-top: 1px solid #4a3a20; }

    .validation-report {
      margin-top: 8px;
      font-size: 11.5px;
      max-height: 160px;
      overflow-y: auto;
      padding: 8px;
      background: rgba(0,0,0,.15);
      border-radius: 8px;
      display: none;
    }

    .validation-report table { margin-top: 6px; }
    .validation-report th { font-size: 10px; padding: 3px 4px; }
    .validation-report td { font-size: 10.5px; padding: 3px 4px; }

    .esiti-card { border: 2px solid var(--brass-hi) !important; }
    .esiti-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
    .esiti-subtitle { font-size: 11px; color: #b3a37e; margin-bottom: 10px; line-height: 1.4; }
    .esiti-grid { display: flex; flex-wrap: wrap; gap: 12px; }
    .esito-item { display: flex; flex-direction: column; align-items: center; gap: 4px; }

    .esito-num {
      position: relative;
      width: 58px;
      height: 58px;
      border-radius: 50%;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      background: radial-gradient(circle at 30% 30%, #f7d774, var(--gold-fill));
      color: #1a1206;
      font-weight: 700;
      cursor: pointer;
      box-shadow: 0 3px 10px rgba(0,0,0,.4);
      transition: transform .15s;
    }

    .esito-num:hover { transform: scale(1.1); }

    .esito-num.top1 {
      background: radial-gradient(circle at 30% 30%, #ff9d6c, #e0552c);
      color: #fff;
      box-shadow: 0 0 14px rgba(224,85,44,.7);
    }

    .esito-num .n { font-size: 21px; font-family: 'Cormorant Garamond', serif; line-height: 1; }
    .esito-num .s { font-size: 9px; font-weight: 600; opacity: .85; }
    .esito-fonti { display: flex; gap: 3px; flex-wrap: wrap; justify-content: center; max-width: 70px; }
    .fonte-dot { width: 8px; height: 8px; border-radius: 50%; }
    .fd-motore { background: #e0b04a; }
    .fd-conv { background: #7fd4d4; }
    .fd-backtest { background: #4fae7a; }
    .fd-manna { background: #e0552c; }
    .fd-piramide { background: #9b59b6; }

    .esiti-legenda { display: flex; gap: 14px; margin-top: 12px; font-size: 10px; color: #b3a37e; flex-wrap: wrap; }
    .esiti-legenda span { display: flex; align-items: center; gap: 5px; }
    .esiti-vuoto { text-align: center; color: #8a7a5c; font-size: 12px; padding: 18px 8px; line-height: 1.6; }
    .esiti-refresh { flex: 0 0 auto !important; padding: 7px 14px !important; }

    .conv-archive-box {
      background: rgba(15,110,110,.12);
      border: 1px solid var(--accent);
      border-radius: 8px;
      padding: 10px;
      margin-bottom: 10px;
    }

    .conv-archive-box .title { font-size: 12px; font-weight: 600; color: #7fd4d4; margin-bottom: 6px; }

    .pred-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 10px; }
    .pred-box { background: rgba(0,0,0,.18); border-radius: 8px; padding: 8px; text-align: center; }
    .pred-box .lbl { font-size: 10.5px; letter-spacing: .08em; color: #c9b98f; text-transform: uppercase; }
    .pred-box .val { font-family: 'Cormorant Garamond', serif; font-size: 20px; font-weight: 700; color: var(--gold-fill); }

    .cand-list { margin-top: 10px; max-height: 180px; overflow-y: auto; }

    .cand-item {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 6px 8px;
      border-radius: 6px;
      background: rgba(0,0,0,.15);
      margin-bottom: 4px;
      font-size: 12.5px;
      cursor: pointer;
      transition: background .15s;
    }

    .cand-item:hover { background: rgba(224,176,74,.18); }

    .cand-item .cn {
      font-family: 'Cormorant Garamond', serif;
      font-size: 17px;
      font-weight: 700;
      color: var(--gold-fill);
      min-width: 28px;
      text-align: center;
    }

    .cm { color: #b3a37e; flex: 1; line-height: 1.35; }
    .cs { color: #4fae7a; font-weight: 600; }

    .fig-badge {
      display: inline-block;
      padding: 2px 9px;
      border-radius: 10px;
      font-size: 11px;
      margin: 2px 3px 2px 0;
      border: 1px solid;
    }

    .firma-badge { cursor: pointer; transition: all .15s; }
    .firma-badge:hover { transform: scale(1.08); }

    .distance-list { max-height: 160px; overflow-y: auto; padding: 4px; }

    .distance-item {
      display: grid;
      grid-template-columns: 1fr auto auto;
      gap: 8px;
      padding: 5px 8px;
      background: rgba(0,0,0,.15);
      border-radius: 6px;
      margin-bottom: 4px;
      font-family: 'Cormorant Garamond', serif;
      font-size: 14px;
    }

    .figure-item {
      display: flex;
      justify-content: space-between;
      padding: 5px 8px;
      background: rgba(140,42,42,.15);
      border-radius: 6px;
      margin-bottom: 4px;
      font-size: 13px;
    }

    .figure-item .type { color: var(--gold-fill); font-weight: 600; }

    .alignment-result {
      padding: 8px;
      background: rgba(15,110,110,.15);
      border-radius: 6px;
      margin-top: 8px;
      font-size: 13px;
      text-align: center;
      min-height: 38px;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .config-input-row { display: flex; gap: 8px; margin-bottom: 8px; }
    .config-input-row input { flex: 1; }

    .config-input-row button {
      background: var(--brass);
      border: 1px solid var(--brass-hi);
      color: #1a1206;
      font-weight: 600;
      padding: 6px 12px;
      border-radius: 6px;
      cursor: pointer;
      font-size: 13px;
    }

    .saved-list { max-height: 140px; overflow-y: auto; }

    .saved-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 6px 8px;
      background: rgba(0,0,0,.15);
      border-radius: 6px;
      margin-bottom: 4px;
      font-size: 13px;
    }

    .saved-item .name { color: var(--brass-hi); font-weight: 500; cursor: pointer; flex: 1; }
    .saved-item .del { color: #e0a0a0; cursor: pointer; padding: 2px 6px; }

    .hist-list { margin-top: 8px; max-height: 120px; overflow-y: auto; }

    .hist-item {
      display: flex;
      align-items: center;
      gap: 6px;
      padding: 5px 8px;
      background: rgba(0,0,0,.15);
      border-radius: 6px;
      margin-bottom: 4px;
      font-size: 12px;
    }

    .hist-item .ruota { color: var(--brass-hi); font-weight: 600; min-width: 60px; }
    .hist-item .nums { font-family: 'Cormorant Garamond', serif; font-size: 14px; flex: 1; }
    .hist-item .del { color: #e0a0a0; cursor: pointer; }

    .conv-item {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 6px 8px;
      border-radius: 6px;
      background: rgba(15,110,110,.14);
      margin-bottom: 4px;
      font-size: 12.5px;
      cursor: pointer;
    }

    .conv-item:hover { background: rgba(15,110,110,.3); }

    .conv-item .cn {
      font-family: 'Cormorant Garamond', serif;
      font-size: 17px;
      font-weight: 700;
      color: #7fd4d4;
      min-width: 28px;
      text-align: center;
    }

    .conv-badge {
      background: var(--accent);
      color: #f4ecd8;
      border-radius: 10px;
      padding: 1px 8px;
      font-size: 11px;
      font-weight: 600;
    }

    .fig-shape { fill-opacity: .10; stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
    .fig-shape-glow { filter: drop-shadow(0 0 5px currentColor); }
    .fig-ray { stroke-width: 1.4; stroke-dasharray: 7 5; opacity: .45; fill: none; }
    .fig-vertex { stroke-width: 2.5; fill-opacity: .95; transition: opacity .2s; }
    .fig-vertex-ring { fill: none; stroke-width: 1.5; opacity: .6; transition: opacity .2s; }
    .fig-center { opacity: .9; }
    .fig-arc { fill: none; stroke-width: 2.2; opacity: .55; stroke-linecap: round; }
    .fig-arc-inner { fill: none; stroke-width: 1.5; opacity: .35; stroke-dasharray: 4 3; }
    .fig-diameter { stroke-width: 2.5; opacity: .7; stroke-linecap: round; }
    .fig-label-bg { fill: rgba(0,0,0,.55); rx: 4; ry: 4; }

    .fig-label {
      font: 700 11px Inter, sans-serif;
      fill: #fff;
      text-anchor: middle;
      dominant-baseline: middle;
      paint-order: stroke;
      stroke: rgba(0,0,0,.6);
      stroke-width: 3px;
    }

    .fig-angle-label { font: 600 9px Inter, sans-serif; fill-opacity: .85; text-anchor: middle; }
    .fig-crosshair { stroke-width: 1.5; opacity: .7; }

    @keyframes figPulse {
      0%, 100% { stroke-opacity: .95; }
      50% { stroke-opacity: .45; }
    }

    .fig-pulse { animation: figPulse 2.6s ease-in-out infinite; }

    .bt-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px; }
    .bt-grid label { font-size: 11px; color: #c9b98f; display: block; margin-bottom: 3px; }
    .bt-result { margin-top: 10px; padding: 12px; background: rgba(0,0,0,.18); border-radius: 8px; font-size: 12px; }
    .bt-row { display: flex; align-items: center; gap: 6px; margin: 6px 0; flex-wrap: wrap; }

    .bt-num {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 36px;
      height: 36px;
      border-radius: 50%;
      font-weight: 700;
      font-family: 'Cormorant Garamond', serif;
      font-size: 16px;
      background: #332512;
      border: 1px solid var(--gold-fill);
      color: var(--gold-fill);
      transition: transform .15s;
    }

    .bt-num:hover { transform: scale(1.08); }
    .bt-num.actual { background: #1a6b3c; border-color: #4fae7a; color: #fff; }
    .bt-num.hit { background: var(--gold-fill); color: #1a1206; border: 2px solid #4fae7a; box-shadow: 0 0 8px rgba(79,174,122,.6); }
    .bt-label { font-size: 11px; color: #c9b98f; margin: 8px 0 4px; }

    input[type=date] {
      background: var(--input-bg);
      border: 1px solid var(--brass);
      color: var(--paper-1);
      border-radius: 8px;
      padding: 8px;
      width: 100%;
      font-family: 'Inter', sans-serif;
      font-size: 13px;
    }

    input[type=date]::-webkit-calendar-picker-indicator { filter: invert(.7); cursor: pointer; }
    .bt-jump { cursor: pointer; text-decoration: underline; color: var(--brass-hi); }

    .notification {
      position: fixed;
      top: 20px;
      right: 20px;
      background: var(--brass);
      color: #1a1206;
      padding: 12px 20px;
      border-radius: 8px;
      font-weight: 600;
      box-shadow: 0 4px 12px rgba(0,0,0,.3);
      opacity: 0;
      transform: translateY(-20px);
      transition: all .3s;
      z-index: 1000;
      pointer-events: none;
    }

    .notification.show { opacity: 1; transform: translateY(0); }

    .guide-modal {
      position: fixed;
      inset: 0;
      background: rgba(0,0,0,.7);
      display: none;
      align-items: center;
      justify-content: center;
      z-index: 2000;
      padding: 16px;
    }

    .guide-modal.open { display: flex; }

    .guide-box {
      background: var(--panel-bg);
      border: 1px solid var(--brass);
      border-radius: 14px;
      max-width: 760px;
      width: 100%;
      max-height: 85vh;
      overflow-y: auto;
      padding: 24px 28px;
    }

    .guide-box h2 { font-family: 'Cormorant Garamond', serif; color: var(--brass-hi); margin: 0 0 12px; }
    .guide-box h4 { color: var(--brass-hi); margin: 16px 0 6px; font-family: 'Cormorant Garamond', serif; font-size: 17px; }
    .guide-box p, .guide-box li { font-size: 13.5px; line-height: 1.6; }
    .guide-close { float: right; background: none; border: 1px solid var(--garnet); color: #e0a0a0; border-radius: 6px; padding: 4px 10px; cursor: pointer; }

    .stats-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; margin-top: 8px; }

    .stat-item {
      text-align: center;
      padding: 8px;
      background: rgba(0,0,0,.15);
      border-radius: 6px;
      cursor: pointer;
      transition: background .15s;
    }

    .stat-item:hover { background: rgba(224,176,74,.18); }
    .stat-item .stat-num { font-size: 18px; font-weight: 700; color: var(--gold-fill); font-family: 'Cormorant Garamond', serif; }
    .stat-item .stat-freq { font-size: 10px; color: #c9b98f; margin-top: 2px; }

    .coerenza-box { margin-top: 10px; padding: 10px; background: rgba(0,0,0,.18); border-radius: 8px; font-size: 12px; }
    .coerenza-item { padding: 4px 6px; margin: 3px 0; background: rgba(79,174,122,.12); border-radius: 4px; color: #4fae7a; }

    .pattern-box { margin-top: 8px; font-size: 11.5px; }

    .pattern-item {
      display: flex;
      justify-content: space-between;
      padding: 5px 8px;
      background: rgba(0,0,0,.15);
      border-radius: 6px;
      margin-bottom: 4px;
    }

    .pattern-item .pair { color: var(--gold-fill); font-weight: 600; font-family: 'Cormorant Garamond', serif; font-size: 14px; }
    .pattern-item .freq { color: #c9b98f; }

    .download-info {
      background: rgba(224,176,74,.15);
      border: 1px solid var(--brass);
      border-radius: 8px;
      padding: 10px;
      margin-bottom: 10px;
      font-size: 11.5px;
      line-height: 1.5;
    }

    .download-info strong { color: var(--brass-hi); }
    .download-info ol { margin: 6px 0; padding-left: 20px; }
    .download-info li { margin: 3px 0; }
    .download-info a { color: var(--brass-hi); text-decoration: underline; }

    .manna-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; margin: 8px 0; }
    .manna-cell { background: rgba(0,0,0,.18); border-radius: 6px; padding: 6px; text-align: center; font-family: 'Cormorant Garamond', serif; font-size: 14px; font-weight: 600; }
    .manna-cell.header { background: rgba(224,176,74,.15); color: var(--brass-hi); font-size: 11px; font-weight: 700; font-family: 'Inter', sans-serif; }
    .manna-cell.result { background: rgba(224,85,44,.2); color: #e0552c; font-size: 16px; font-weight: 700; border: 1px solid #e0552c; }
    .manna-cell.diff { background: rgba(79,174,122,.15); color: #4fae7a; font-size: 12px; }

    .piramide { display: flex; flex-direction: column; align-items: center; gap: 4px; margin: 10px 0; }
    .piramide-row { display: flex; gap: 4px; justify-content: center; }

    .piramide-num {
      background: rgba(155,89,182,.15);
      border: 1px solid #9b59b6;
      border-radius: 6px;
      padding: 4px 8px;
      font-family: 'Cormorant Garamond', serif;
      font-size: 14px;
      font-weight: 600;
      color: #9b59b6;
      min-width: 32px;
      text-align: center;
    }

    .piramide-num.vertice {
      background: rgba(155,89,182,.3);
      border: 2px solid #9b59b6;
      font-size: 18px;
      box-shadow: 0 0 10px rgba(155,89,182,.4);
    }

    .spia-card { background: rgba(224,85,44,.08); border: 1px solid #e0552c; border-radius: 8px; padding: 10px; margin-top: 8px; }
    .spia-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }

    .spia-num {
      background: #e0552c;
      color: #fff;
      border-radius: 50%;
      width: 32px;
      height: 32px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: 'Cormorant Garamond', serif;
      font-weight: 700;
      font-size: 16px;
    }

    .spia-results { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }

    .spia-item {
      background: rgba(0,0,0,.2);
      border-radius: 6px;
      padding: 6px;
      text-align: center;
      cursor: pointer;
      transition: background .15s;
    }

    .spia-item:hover { background: rgba(224,176,74,.18); }
    .spia-item .num { font-family: 'Cormorant Garamond', serif; font-size: 16px; font-weight: 700; color: var(--gold-fill); }
    .spia-item .pct { font-size: 10px; color: #c9b98f; }

    .cadenze-grid { display: grid; grid-template-columns: repeat(10, 1fr); gap: 4px; margin: 8px 0; }
    .cadenza-cell { background: rgba(0,0,0,.18); border-radius: 4px; padding: 6px 2px; text-align: center; font-size: 11px; }
    .cadenza-cell .num { font-family: 'Cormorant Garamond', serif; font-weight: 700; font-size: 14px; color: var(--gold-fill); }
    .cadenza-cell .count { color: #c9b98f; font-size: 9px; margin-top: 2px; }
    .cadenza-cell.cold { background: rgba(224,160,160,.15); border: 1px solid #e0a0a0; }
    .cadenza-cell.hot { background: rgba(79,174,122,.15); border: 1px solid #4fae7a; }

    .figure-delay { margin-top: 10px; }

    .fig-delay-item {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 6px 8px;
      background: rgba(0,0,0,.15);
      border-radius: 6px;
      margin-bottom: 4px;
      font-size: 12px;
    }

    .fig-delay-item .fname { color: var(--brass-hi); font-weight: 600; flex: 1; }
    .fig-delay-item .fdelay { font-family: 'Cormorant Garamond', serif; font-size: 14px; font-weight: 700; }
    .fig-delay-item.alert { background: rgba(224,85,44,.15); border: 1px solid #e0552c; }
    .fig-delay-item.alert .fdelay { color: #e0552c; }

    .l38-col { display: grid; grid-template-columns: 1fr 1fr 1fr auto; gap: 4px; align-items: center; margin-bottom: 4px; }
    .l38-cell { background: rgba(0,0,0,.18); border-radius: 6px; padding: 5px; text-align: center; font-family: 'Cormorant Garamond', serif; font-size: 15px; font-weight: 600; }
    .l38-cell.ambo { color: var(--gold-fill); border: 1px solid var(--gold-fill); }
    .l38-cell.escl { color: #e0a0a0; text-decoration: line-through; opacity: .7; }
    .l38-swap { background: #332512; border: 1px solid var(--brass); color: var(--paper-1); border-radius: 6px; cursor: pointer; font-size: 11px; padding: 4px 6px; }

    .shrink-line {
      font-family: 'Courier New', monospace;
      font-size: 10.5px;
      color: #b3a37e;
      padding: 3px 6px;
      background: rgba(0,0,0,.15);
      border-radius: 4px;
      margin-bottom: 3px;
    }

    .shrink-line .ok { color: #4fae7a; }
    .shrink-line .no { color: #e0a0a0; }
    /* v2.10: Doppie Distanze */
    .dist-card{border:2px solid #0f6e6e;margin-top:12px;}
    .dist-result{margin-top:10px;padding:10px;background:rgba(15,110,110,.12);border-radius:8px;}
    .dist-distanze{display:flex;flex-wrap:wrap;gap:4px;margin:6px 0;}
    .dist-tag{background:rgba(224,176,74,.15);border:1px solid var(--gold-fill);color:var(--gold-fill);
    font-family:'Cormorant Garamond',serif;font-weight:600;font-size:13px;padding:2px 8px;border-radius:10px;}
    .dist-tag.double{background:rgba(224,85,44,.18);border-color:#e0552c;color:#e0552c;}
  </style>
</head>
<body>
  <div class="wrap">
    <h1>Cerchio di Spilimbergo — Suite Completa v2.10</h1>
    <p class="subtitle">🔍 Doppie Distanze · Logica 38 · Rx = ritardo MAX storico · Data da calendario · Tracker per ruota · Vertici Interattivi · Fuori 90</p>

    <div class="toolbar">
      <button id="themeToggle">🌙 Tema</button>
      <button id="shareBtn">🔗 Condividi</button>
      <button id="importDemoBtn">📥 Demo</button>
      <button id="clearAllBtn">🗑️ Pulisci Tutto</button>
      <button id="guideBtn">❓ Guida</button>
    </div>

    <div class="layout">
      <div class="wheel-card">
        <svg id="wheel" viewBox="0 0 700 700"></svg>
      </div>
    

      <div class="panel">
        <div class="card" data-ring="0">
          <h3><span class="swatch" style="background:#8c2a2a"></span>Anello interno</h3>
          <div class="btn-row">
            <button class="step" data-ring="0" data-dir="-1">↺</button>
            <button class="step" data-ring="0" data-dir="1">↻</button>
          </div>
        </div>

        <div class="card" data-ring="1">
          <h3><span class="swatch" style="background:#8c2a2a"></span>Anello medio</h3>
          <div class="btn-row">
            <button class="step" data-ring="1" data-dir="-1">↺</button>
            <button class="step" data-ring="1" data-dir="1">↻</button>
          </div>
        </div>

        <div class="card" data-ring="2">
          <h3><span class="swatch" style="background:#8c2a2a"></span>Anello esterno</h3>
          <div class="btn-row">
            <button class="step" data-ring="2" data-dir="-1">↺</button>
            <button class="step" data-ring="2" data-dir="1">↻</button>
          </div>
        </div>

        <div class="card">
          <h3>Lettura ai 4 punti cardinali</h3>
          <table>
            <tr><th></th><th>N</th><th>S</th><th>E</th><th>O</th></tr>
            <tr><td class="ring-name-cell">Interno</td><td id="r0-N"></td><td id="r0-S"></td><td id="r0-E"></td><td id="r0-O"></td></tr>
            <tr><td class="ring-name-cell">Medio</td><td id="r1-N"></td><td id="r1-S"></td><td id="r1-E"></td><td id="r1-O"></td></tr>
            <tr><td class="ring-name-cell">Esterno</td><td id="r2-N"></td><td id="r2-S"></td><td id="r2-E"></td><td id="r2-O"></td></tr>
          </table>
          <button class="step danger" id="resetBtn" style="margin-top:8px;width:100%;">Riporta le ghiere a riposo</button>
        </div>

        <div class="card" style="border:2px solid #9b59b6;">
          <h3>🧭 Logica 38 — Colonne di Cadenza <span style="font-size:11px;color:#8a7a5c;">v2.9</span></h3>
          <div class="bt-grid">
            <div><label>Ruota</label><select id="l38Ruota"></select></div>
            <div><label>Primo estratto</label><input type="number" id="l38Num" min="1" max="90" placeholder="es. 38"></div>
            <div>
              <label>Derivazione cadenza</label>
              <select id="l38Mode">
                <option value="radice">Radice (somma cifre)</option>
                <option value="compl">Complemento 90</option>
                <option value="manuale">Manuale</option>
              </select>
            </div>
            <div><label>Cadenza</label><input type="number" id="l38Cad" min="0" max="9" disabled></div>
            <div style="grid-column:1/-1;"><label>Data analisi (calendario)</label><input type="date" id="l38Date"></div>
          </div>
          <div class="btn-row" style="margin-top:8px;">
            <button class="step primary" id="l38Run">🧭 Imposta cerchio & colonne</button>
          </div>
          <div id="l38Cols"></div>
          <div class="btn-row" style="margin-top:6px;">
            <button class="step" id="l38Shrink">📉 Shrink 9→2</button>
            <button class="step" id="l38Terzine">🧩 Terzine 280</button>
            <button class="step" id="l38Save">💾 Salva (9 conc.)</button>
          </div>
          <div style="font-size:10px;color:#8a7a5c;margin-top:6px;line-height:1.5;">
            Rc = ritardo corrente (ambo nella combinazione) · Rx = <b>massimo ritardo storico</b> mai registrato dalla combinazione (filtro Rc ≥ Rx = "mai stata così ritardata"). Shrink e Terzine usano l'archivio fino alla data scelta.
          </div>
          <div id="l38Results"></div>
          <div id="l38Tracker"></div>
        </div>
      
        <!-- v2.9.1: Verifica Estrazioni per Periodo -->
        <div class="card" style="border:2px solid #4fae7a;margin-top:12px;">
          <h3>📅 Verifica Estrazioni per Periodo</h3>
          <div class="bt-grid">
            <div><label>Dal</label><input type="date" id="verDataDal"></div>
            <div><label>Al</label><input type="date" id="verDataAl"></div>
          </div>
          <div class="btn-row" style="margin-top:8px;">
            <button class="step primary" id="verEstrazioni" style="flex:1;"> Conta Estrazioni</button>
          </div>
          <div id="verRisultato" style="margin-top:10px;font-size:12px;color:#c9b98f;"></div>
        </div>
      
        <!-- v2.10: RICERCA AUTOMATICA PER DOPPIE DISTANZE -->
        <div class="card dist-card">
        <h3>🔍 Doppie Distanze <span style="font-size:11px;color:#8a7a5c;">v2.10</span></h3>
        <div class="esiti-subtitle">Cerca terzine sulla ruota target che hanno le doppie distanze dell'estrazione sorgente, con filtro Rc ≥ Rx.</div>
        <div class="bt-grid">
        <div><label>Ruota Sorgente</label><select id="distRuotaSorgente"></select></div>
        <div><label>Ruota Target</label><select id="distRuotaTarget"></select></div>
        <div style="grid-column:1/-1;">
        <label>Numeri estrazione sorgente (es. 26 83 40 69 53)</label>
        <input type="text" id="distNumeriSorgente" placeholder="Inserisci 5 numeri separati da spazio">
        </div>
        </div>
        <div class="btn-row" style="margin-top:8px;">
        <button class="step primary" id="distRun" style="flex:1;">🔍 Cerca Terzine Ottimali</button>
        </div>
        <div id="distDistanze"></div>
        <div id="distResults"></div>
        </div>

        <div class="card">
          <h3>📊 Statistiche & Pattern</h3>
          <div class="btn-row">
            <select id="statsRuota" style="flex:1;"></select>
            <button class="step" id="statsRun" style="flex:0 0 auto;padding:8px 14px;">Analizza</button>
          </div>
          <div class="btn-row" style="margin-top:6px;">
            <button class="step" id="statsTop10">Top 10 Frequenti</button>
            <button class="step" id="statsPattern">Pattern Coppie</button>
          </div>
          <div id="statsResults"></div>
        </div>

        <div class="card" id="archiveCard">
          <h3>📚 Archivio Estrazioni</h3>
          <div class="download-info">
            <strong>📥 Come caricare l'archivio:</strong>
            <ol>
              <li>Scarica manualmente il file: <a href="https://www.brightstarlottery.it/STORICO_ESTRAZIONI_LOTTO/storico.zip" target="_blank">storico.zip</a></li>
              <li>Clicca su <strong>"📂 Carica ZIP Locale"</strong> qui sotto</li>
              <li>Seleziona il file ZIP appena scaricato</li>
              <li>Il sistema lo decomprimerà e creerà la cache automaticamente</li>
            </ol>
            <em>💡 La cache resta salvata nel browser per usi futuri!</em>
          </div>
          <div class="archive-info">
            <span class="status-dot status-no" id="cacheStatusDot"></span>
            <span id="cacheInfoText">Nessuna cache</span>
          </div>
          <div class="btn-row">
            <button class="step primary" id="btnLoadLocal">📂 Carica ZIP Locale</button>
            <input type="file" id="fileInput" accept=".zip" style="display:none;">
          </div>
          <div class="btn-row" style="margin-top:6px;">
            <button class="step" id="btnLoadCache" disabled>💾 Carica Cache</button>
            <button class="step" id="btnValida" disabled>🔍 Valida</button>
            <button class="step danger" id="btnClearCache">🗑️ Cancella</button>
          </div>
          <div class="progress-bar-bg" id="progressWrap">
            <div class="progress-bar-fill" id="progressBar"></div>
          </div>
          <div class="progress-label" id="progressLabel"></div>
          <div class="archive-log" id="archiveLog"></div>
          <div class="validation-report" id="validationReport"></div>
          <div class="archive-use">
            <label style="font-size:11px;color:#c9b98f;display:block;margin-bottom:3px;">Epoca storica</label>
            <select id="epocaSelect" style="margin-bottom:6px;">
              <option value="completa">Archivio Completo</option>
              <option value="venus">Venus (2005-oggi)</option>
              <option value="quattro">4 Estrazioni (2023-oggi)</option>
            </select>
            <label style="font-size:11px;color:#c9b98f;display:block;margin-bottom:3px;">Usa archivio sul Cerchio</label>
            <div class="btn-row"><select id="archivioRuotaSelect" style="flex:1;"></select></div>
            <div class="btn-row" style="margin-top:6px;">
              <button class="step" id="btnUseLast1" disabled>🎯 Ultima estrazione</button>
              <button class="step" id="btnUseLast3" disabled>🎯 Ultime 3</button>
            </div>
          </div>
        </div>

        <div class="card esiti-card">
          <div class="esiti-header">
            <h3 style="margin:0;">🎯 Numeri Promettenti</h3>
            <button class="step primary esiti-refresh" id="esitiRefresh">↻ Aggiorna</button>
          </div>
          <div class="esiti-subtitle">
            Aggrega Motore, Convergenze, Backtest, Manna e Piramide con <b>regola del Fuori 90</b> applicata a tutti i calcoli.
            Arancio = top assoluto. Clicca un pallino per evidenziarlo sul Cerchio.
          </div>
          <div id="esitiContent">
            <div class="esiti-vuoto">
              Esegui almeno un'analisi (⚡ Motore, 🔀 Convergenze, 🕰️ Backtest, 🎲 Manna o 🔺 Piramide)<br>
              poi premi <b>↻ Aggiorna</b> per vedere i numeri promettenti.
            </div>
          </div>
        </div>

        <div class="card">
          <h3>🎲 Metodo delle 3 Estrazioni <span style="font-size:11px;color:#8a7a5c;">Manna</span></h3>
          <div class="btn-row">
            <select id="mannaRuota" style="flex:1;"></select>
            <button class="step primary" id="mannaRun" style="flex:0 0 auto;padding:8px 14px;">Calcola</button>
          </div>
          <div id="mannaResults"></div>
        </div>

        <div class="card">
          <h3>🔺 Piramide Ciclotimica</h3>
          <div class="esiti-subtitle">Costruisce la piramide dalle differenze successive dei numeri evidenziati (VERDI o ORO).</div>
          <div class="btn-row">
            <button class="step primary" id="piramideRun">🔺 Genera Piramide</button>
            <button class="step" id="piramideClear">🧹</button>
          </div>
          <div id="piramideResults"></div>
        </div>

        <div class="card">
          <h3>🕵️ Spie per Ruota</h3>
          <div class="esiti-subtitle">Analizza i numeri evidenziati e mostra quali numeri "annunciano" storicamente nelle 5 estrazioni successive.</div>
          <div class="btn-row">
            <select id="spieRuota" style="flex:1;"></select>
            <button class="step primary" id="spieRun" style="flex:0 0 auto;padding:8px 14px;">Analizza Spie</button>
          </div>
          <div id="spieResults"></div>
        </div>

        <div class="card">
          <h3>⏰ Figure Mancanti (Ritardo Figure)</h3>
          <div class="esiti-subtitle">Mostra quali figure geometriche non escono da più tempo sulla ruota scelta.</div>
          <div class="btn-row">
            <select id="figDelayRuota" style="flex:1;"></select>
            <button class="step" id="figDelayRun" style="flex:0 0 auto;padding:8px 14px;">Analizza</button>
          </div>
          <div id="figDelayResults"></div>
        </div>

        <div class="card">
          <h3>🎯 Cadenze & Decine</h3>
          <div class="esiti-subtitle">Analizza la distribuzione dei numeri evidenziati per cadenza (cifra finale) e decina.</div>
          <div id="cadenzeResults">
            <div style="text-align:center;color:#8a7a5c;font-size:12px;padding:8px;">Evidenzia almeno 2 numeri per vedere l'analisi</div>
          </div>
        </div>

        <div class="card" id="backtestCard">
          <h3>️ Verifica a Ritroso</h3>
          <div class="bt-grid">
            <div><label>Ruota</label><select id="btRuotaSelect"></select></div>
            <div><label>Data (calendario)</label><input type="date" id="btDateInput"></div>
            <div><label>Estrazioni base</label><input type="number" id="btNumBase" value="60" min="10" max="500"></div>
            <div><label>Top candidati</label><input type="number" id="btTopN" value="10" min="5" max="45"></div>
          </div>
          <div class="btn-row" style="margin-top:8px;">
            <button class="step primary" id="btRun">🔍 Verifica a ritroso</button>
          </div>
          <div id="btResults"></div>
        </div>

        <div class="card">
          <h3>🤖 Motore Previsivo <span style="font-size:11px;color:#8a7a5c;">L1 · Vertici Interattivi</span></h3>
          <div class="btn-row">
            <button class="step primary" id="engineRun">⚡ Genera figure e previsione</button>
          </div>
          <div class="btn-row" style="margin-top:6px;">
            <button class="step" id="heatToggle">🔥 Calore</button>
            <button class="step" id="tourBtn"> Tour</button>
            <button class="step" id="engineClear">🧹</button>
          </div>
          <div id="engineResults"></div>
          <div style="font-size:10.5px;color:#8a7a5c;font-style:italic;margin-top:8px;text-align:center;">
            Metodo tradizionale: solo studio e intrattenimento. Gioca responsabilmente.
          </div>
        </div>

        <div class="card">
          <h3>📜 Storico & Convergenze <span style="font-size:11px;color:#8a7a5c;">L2</span></h3>
          <div class="conv-archive-box">
            <div class="title">📥 Carica ruote dall'archivio</div>
            <div class="btn-row">
              <select id="convArchivioRuota" style="flex:1;"></select>
              <input type="number" id="convArchivioN" value="10" min="2" max="100" style="width:70px;">
            </div>
            <button class="step" id="btnCaricaRuoteConv" style="margin-top:6px;width:100%;">📥 Carica estrazioni nelle Convergenze</button>
          </div>
          <textarea id="historyInput" rows="3" placeholder="Oppure inserisci a mano, una estrazione per riga:
Bari 7 23 45 68 81
Napoli 12 34 56 72 90"></textarea>
          <div class="btn-row" style="margin-top:6px;">
            <button class="step" id="histLoad">📥 Carica</button>
            <button class="step" id="histDemo">Demo</button>
            <button class="step" id="histClear">🧹</button>
          </div>
          <div class="hist-list" id="histList"></div>
          <div class="btn-row" style="margin-top:6px;">
            <select id="ruotaFilter"></select>
            <button class="step primary" id="convRun">🔀 Convergenze</button>
          </div>
          <div id="convResults"></div>
        </div>

        <div class="card">
          <h3>Estrazione da analizzare</h3>
          <textarea id="drawInput" rows="2" placeholder="Incolla i numeri: 4 26 38 60 61"></textarea>
          <div class="btn-row" style="margin-top:6px;">
            <button class="step" id="drawGo">Evidenzia</button>
            <button class="step" id="drawClear">Pulisci</button>
          </div>
          <div class="chips" id="drawChips"></div>
          <div id="coerenzaBox"></div>
        </div>

        <div class="card">
          <h3>📐 Distanze e Figure</h3>
          <div class="distance-list" id="distanceList">
            <div style="text-align:center;color:#8a7a5c;font-size:12px;padding:8px;">Evidenzia almeno 2 numeri</div>
          </div>
          <div id="figureList"></div>
        </div>

        <div class="card">
          <h3>🎯 Cerca Allineamenti <span style="font-size:11px;color:#8a7a5c;">potenziato</span></h3>
          <div class="btn-row" style="margin-bottom:6px;">
            <select id="alignMode" style="flex:1;">
              <option value="entrambi">🟡 Oro + 🟢 Verde</option>
              <option value="oro">🟡 Solo numeri evidenziati</option>
              <option value="verde"> Solo estrazione caricata</option>
            </select>
          </div>
          <button class="step primary" id="findAlignmentBtn" style="width:100%;">🔍 Trova migliori configurazioni</button>
          <div class="alignment-result" id="alignmentResult">
            <span style="color:#8a7a5c;font-size:12px;">Evidenzia numeri o carica una estrazione, poi cerca</span>
          </div>
          <div id="alignTopList"></div>
          <div id="alignDetail"></div>
        </div>

        <div class="card">
          <h3>💾 Configurazioni</h3>
          <div class="config-input-row">
            <input type="text" id="configNameInput" placeholder="Nome configurazione">
            <button id="saveConfigBtn">Salva</button>
          </div>
          <div class="saved-list" id="savedList"></div>
        </div>

        <div class="card">
          <h3>Scova numero</h3>
          <div class="btn-row">
            <input type="number" id="finderInput" min="1" max="90" placeholder="1-90">
            <button class="step" id="finderGo" style="flex:0 0 auto;padding:8px 14px;">Trova</button>
          </div>
          <div class="chips" id="chips"></div>
          <button class="step danger" id="clearHighlights" style="width:100%;margin-top:8px;">Pulisci evidenziazioni</button>
        </div>

        <p class="hint">
          v2.9: Rx = massimo ritardo storico della combinazione (come script "Lunghetta per Ritardo Max"); data da calendario nella Logica 38; tracker solo ruota selezionata con eliminazione. v2.7: mouse sui vertici per leggere i numeri coperti.
        </p>
      </div>
    </div>
  </div>

  <div class="guide-modal" id="guideModal">
    <div class="guide-box">
      <button class="guide-close" id="guideClose">✕ Chiudi</button>
      <h2>Guida alla Suite Completa v2.9</h2>
      <h4>Rc / Rx corretti (NOVITÀ v2.9)</h4>
      <p><b>Rc</b> = ritardo corrente della combinazione (concorsi dall'ultimo ambo realizzato al suo interno). <b>Rx</b> = <b>massimo ritardo storico</b> mai registrato dalla stessa combinazione sull'archivio della ruota fino alla data scelta (il più lungo intervallo passato senza ambi). Il filtro <b>Rc ≥ Rx</b> segnala combinazioni "mai state così ritardate". Per i gruppi di 3 terzine: Rc = somma dei ritardi correnti, Rx = somma delle attese empiriche.</p>
      <h4>Data da calendario (NOVITÀ v2.9)</h4>
      <p>Nella Logica 38 puoi fissare la data di analisi: shrink e terzine vengono calcolati sull'archivio <b>fino a quella data</b>, come nello script originale. Il tracker verifica invece sempre sulle ultime 9 estrazioni reali.</p>
      <h4>Tracker per ruota (NOVITÀ v2.9)</h4>
      <p>Il riepilogo previsioni mostra <b>solo la ruota selezionata</b>; ogni riga ha ✕ per eliminarla e c'è ️ per cancellarle tutte in blocco.</p>
      <h4>Logica 38 — Colonne di Cadenza (v2.8)</h4>
      <p>Imposta ruota e primo estratto (es. Genova 38). Cadenza da radice/complemento/manuale; la novina occupa 3 colonne sul cerchio: ambo = interno+medio, escluso = esterno (⇄ scambia). 📉 Shrink 9→2 e 🧩 280 terzine; 💾 salva con durata 9 concorsi.</p>
      <h4>Vertici Interattivi (v2.7)</h4>
      <p>Mouse/tap su un vertice: il numero coperto appare ingrandito e leggibile.</p>
      <h4>Figure Selezionabili (v2.6) · Sviluppo Dinamico (v2.5) · Fuori 90</h4>
      <p>Clicca una Δ della firma per vedere solo quella figura; i calcoli &gt;90 normalizzati in 1-90.</p>
      <h4>Altri moduli</h4>
      <p>Archivio ZIP+IndexedDB · Manna · Piramide · Spie · Figure Mancanti · Cadenze & Decine · Backtest · Convergenze · Allineamenti · Numeri Promettenti.</p>
      <h4>⚠️ Nota importante</h4>
      <p>Metodi della tradizione lottologica, privi di validità scientifica. Il Lotto è casuale. Gioca responsabilmente.</p>
    </div>
  </div>

  <div class="notification" id="notification"></div>

  <script>
    // ============================================================
    // PARAMETRI E COSTANTI
    // ============================================================
    const RUOTE_NOME = {
      'BA': 'Bari', 'CA': 'Cagliari', 'FI': 'Firenze', 'GE': 'Genova', 'MI': 'Milano',
      'NA': 'Napoli', 'PA': 'Palermo', 'RM': 'Roma', 'RN': 'Nazionale', 'TO': 'Torino', 'VE': 'Venezia'
    };
    const CODICE_TO_NOME = {
      'BA': 'Bari', 'CA': 'Cagliari', 'FI': 'Firenze', 'GE': 'Genova', 'MI': 'Milano',
      'NA': 'Napoli', 'PA': 'Palermo', 'RM': 'Roma', 'RN': 'Nazionale', 'TO': 'Torino', 'VE': 'Venezia'
    };
    const EPOCHE = {
      'venus': { nome: 'Venus (2005-oggi)', dataInizio: '2005-05-05' },
      'quattro': { nome: '4 Estrazioni (2023-oggi)', dataInizio: '2023-07-07' },
      'completa': { nome: 'Archivio Completo', dataInizio: null }
    };

    let archivio = null;
    let LAST_BACKTEST = [];
    let MANNA_RESULTS = [];
    let PIRAMIDE_RESULTS = [];

    // ============================================================
    // REGOLA DEL FUORI 90
    // ============================================================
    function fuori90(n) {
      n = Math.round(n);
      while (n > 90) n -= 90;
      while (n <= 0) n += 90;
      return n;
    }

    // ============================================================
    // DISTANZA CICLOMETRICA MINIMA (v2.5)
    // ============================================================
    function distanzaCiclo(a, b) {
      const d = Math.abs(a - b);
      return Math.min(d, 90 - d);
    }

    // ============================================================
    // FIRMA GEOMETRICA (v2.5)
    // ============================================================
    function firmaGeometrica(nums) {
      const freq = new Map();
      for (let i = 0; i < nums.length; i++) {
        for (let j = i + 1; j < nums.length; j++) {
          const d = distanzaCiclo(nums[i], nums[j]);
          if (!freq.has(d)) freq.set(d, { d, count: 0, pairs: [] });
          const entry = freq.get(d);
          entry.count++;
          entry.pairs.push([nums[i], nums[j]]);
        }
      }
      return [...freq.values()].sort((a, b) => b.count - a.count || a.d - b.d);
    }

    // ============================================================
    // ARCHIVIO: CACHE SU INDEXEDDB
    // ============================================================
    const DB_NAME = 'icq_archivio_db';
    const DB_STORE = 'estrazioni';
    const CACHE_META_KEY = 'archivio_estrazioni_meta';

    function apriDB() {
      return new Promise((resolve, reject) => {
        const req = indexedDB.open(DB_NAME, 1);
        req.onupgradeneeded = e => {
          const db = e.target.result;
          if (!db.objectStoreNames.contains(DB_STORE)) db.createObjectStore(DB_STORE, { keyPath: 'id' });
        };
        req.onsuccess = e => resolve(e.target.result);
        req.onerror = () => reject(req.error);
      });
    }

    function cacheEsiste() { return localStorage.getItem(CACHE_META_KEY) !== null; }

    function cacheUltimaData() {
      const m = localStorage.getItem(CACHE_META_KEY);
      return m ? JSON.parse(m).ultimaData : null;
    }

    async function salvaCache(righe, ultimaData) {
      try {
        const db = await apriDB();
        const compatte = righe.map(r => [r.data, r.ruota, r.n1, r.n2, r.n3, r.n4, r.n5]);
        await new Promise((resolve, reject) => {
          const tx = db.transaction(DB_STORE, 'readwrite');
          tx.objectStore(DB_STORE).put({ id: 'archivio', righe: compatte, ultimaData, salvatoIl: new Date().toISOString() });
          tx.oncomplete = () => resolve();
          tx.onerror = () => reject(tx.error);
        });
        db.close();
        localStorage.setItem(CACHE_META_KEY, JSON.stringify({ ultimaData, nRighe: righe.length }));
        if (navigator.storage && navigator.storage.persist) navigator.storage.persist();
        logArchivio(`✅ Cache salvata (IndexedDB): ${righe.length.toLocaleString('it-IT')} righe, ultima: ${ultimaData}`, 'ok');
        return true;
      } catch (e) {
        logArchivio(`❌ Errore salvataggio IndexedDB: ${e.message}`, 'err');
        return false;
      }
    }

    async function caricaCache() {
      try {
        const db = await apriDB();
        const dati = await new Promise((resolve, reject) => {
          const tx = db.transaction(DB_STORE, 'readonly');
          const req = tx.objectStore(DB_STORE).get('archivio');
          req.onsuccess = () => resolve(req.result);
          req.onerror = () => reject(req.error);
        });
        db.close();
        if (!dati) return null;
        const righe = dati.righe.map(a => ({ data: a[0], ruota: a[1], n1: a[2], n2: a[3], n3: a[4], n4: a[5], n5: a[6] }));
        archivio = { righe, ultimaData: dati.ultimaData };
        logArchivio(`📂 Cache caricata (IndexedDB): ${righe.length.toLocaleString('it-IT')} righe, ultima: ${dati.ultimaData}`, 'ok');
        return { righe, ultimaData: dati.ultimaData };
      } catch (e) {
        logArchivio(`❌ Errore lettura cache: ${e.message}`, 'err');
        return null;
      }
    }

    async function cancellaCache() {
      try {
        const db = await apriDB();
        await new Promise(resolve => {
          const tx = db.transaction(DB_STORE, 'readwrite');
          tx.objectStore(DB_STORE).delete('archivio');
          tx.oncomplete = () => resolve();
          tx.onerror = () => resolve();
        });
        db.close();
      } catch (e) {}
      localStorage.removeItem(CACHE_META_KEY);
      archivio = null;
      document.getElementById('validationReport').style.display = 'none';
      updateArchiveUI();
      logArchivio('🗑️ Cache cancellata', 'warn');
      showNotification('🗑️ Cache cancellata');
    }

    function logArchivio(msg, tipo = '') {
      const log = document.getElementById('archiveLog');
      log.style.display = 'block';
      const cls = tipo === 'ok' ? 'log-ok' : tipo === 'warn' ? 'log-warn' : tipo === 'err' ? 'log-err' : '';
      log.innerHTML += `<div class="${cls}">[${new Date().toLocaleTimeString('it-IT')}] ${msg}</div>`;
      log.scrollTop = log.scrollHeight;
    }

    function setProgress(pct, label) {
      document.getElementById('progressWrap').style.display = pct > 0 ? 'block' : 'none';
      document.getElementById('progressBar').style.width = pct + '%';
      document.getElementById('progressLabel').textContent = label || '';
    }

    function updateArchiveUI() {
      const esiste = cacheEsiste() && archivio;
      const dot = document.getElementById('cacheStatusDot');
      const info = document.getElementById('cacheInfoText');
      if (esiste) {
        dot.className = 'status-dot status-ok';
        info.textContent = `${archivio.righe.length.toLocaleString('it-IT')} righe · ultima: ${archivio.ultimaData}`;
        document.getElementById('btnValida').disabled = false;
        document.getElementById('btnUseLast1').disabled = false;
        document.getElementById('btnUseLast3').disabled = false;
      } else {
        dot.className = 'status-dot status-no';
        info.textContent = cacheEsiste() ? 'Cache presente (premi Carica Cache)' : 'Nessuna cache';
        document.getElementById('btnLoadCache').disabled = !cacheEsiste();
        document.getElementById('btnValida').disabled = !cacheEsiste();
        document.getElementById('btnUseLast1').disabled = true;
        document.getElementById('btnUseLast3').disabled = true;
      }
    }

    function popolaSelectRuote() {
      const options = Object.entries(RUOTE_NOME).map(([k, v]) => `<option value="${k}">${k} - ${v}</option>`).join('');
      document.getElementById('archivioRuotaSelect').innerHTML = options;
      document.getElementById('statsRuota').innerHTML = options;
      document.getElementById('mannaRuota').innerHTML = options;
      document.getElementById('spieRuota').innerHTML = options;
      document.getElementById('figDelayRuota').innerHTML = options;
      document.getElementById('l38Ruota').innerHTML = options;
    }

    function parseData(s) {
      if (!s) return null;
      s = String(s).trim();
      let m = s.match(/^(\d{4})[-\/](\d{1,2})[-\/](\d{1,2})/);
      if (m) { const d = new Date(+m[1], +m[2] - 1, +m[3]); return isNaN(d.getTime()) ? null : d; }
      m = s.match(/^(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})/);
      if (m) { const d = new Date(+m[3], +m[2] - 1, +m[1]); return isNaN(d.getTime()) ? null : d; }
      const d = new Date(s);
      return isNaN(d.getTime()) ? null : d;
    }

    function toISOData(d) {
      return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
    }

    function validaEstrazione(riga, ruoteViste, dateViste) {
      const errors = [];
      const nums = [riga.n1, riga.n2, riga.n3, riga.n4, riga.n5];
      if (nums.some(n => isNaN(n) || n < 1 || n > 90)) errors.push('Numeri fuori range 1-90');
      if (new Set(nums).size !== nums.length) errors.push('Numeri duplicati nella stessa estrazione');
      const key = `${riga.data}_${riga.ruota}`;
      if (dateViste.has(key)) errors.push('Estrazione duplicata');
      else dateViste.add(key);
      if (!CODICE_TO_NOME[riga.ruota]) errors.push('Codice ruota non valido');
      return { valid: errors.length === 0, errors };
    }

    async function caricaArchivioLocale(file) {
      setProgress(10, 'Decomprimo ZIP...');
      logArchivio(`📦 Decomprimo ${file.name}...`);
      try {
        const zip = await JSZip.loadAsync(file);
        const storicoFile = zip.file('storico.txt');
        if (!storicoFile) throw new Error('File storico.txt non trovato nel ZIP');
        const raw = await storicoFile.async('text');
        setProgress(50, 'Analizzo estrazioni...');
        logArchivio(`📄 Analisi file storico.txt...`);
        const frames = [];
        const ruoteViste = new Set();
        const dateViste = new Set();
        for (const line of raw.split('\n')) {
          const trimmed = line.trim();
          if (!trimmed) continue;
          const parti = trimmed.split('\t');
          if (parti.length !== 7) continue;
          const [dataStr, codiceRuota, ...numsStr] = parti;
          if (!CODICE_TO_NOME[codiceRuota]) continue;
          const nums = numsStr.map(Number);
          const d = parseData(dataStr);
          if (!d) continue;
          const riga = { data: dataStr, ruota: codiceRuota, n1: nums[0], n2: nums[1], n3: nums[2], n4: nums[3], n5: nums[4] };
          const validazione = validaEstrazione(riga, ruoteViste, dateViste);
          if (!validazione.valid) continue;
          frames.push(riga);
        }
        setProgress(80, 'Ordino e salvo...');
        if (!frames.length) {
          logArchivio('❌ Nessun dato valido trovato', 'err');
          showNotification('❌ Nessun dato valido nel file');
          setProgress(100, 'Completato');
          return;
        }
        frames.sort((a, b) => parseData(a.data) - parseData(b.data) || a.ruota.localeCompare(b.ruota));
        archivio = { righe: frames, ultimaData: frames[frames.length - 1].data };
        const ok = await salvaCache(frames, archivio.ultimaData);
        if (!ok) {
          logArchivio('⚠️ Dati disponibili SOLO per la sessione corrente', 'warn');
          showNotification('️ Salvataggio non riuscito: dati solo per questa sessione', 4000);
        }
        updateArchiveUI();
        setupBtDateRange();
        setupL38DateRange();
        renderL38Tracker();
        showNotification(`📥 ${archivio.righe.length.toLocaleString('it-IT')} righe in archivio`);
      } catch (e) {
        logArchivio(`❌ Errore: ${e.message}`, 'err');
        showNotification(`❌ Errore: ${e.message}`);
      }
      setProgress(100, 'Completato');
    }

    function validaDati() {
      if (!archivio || !archivio.righe.length) { showNotification('️ Nessun dato'); return; }
      const box = document.getElementById('validationReport');
      box.style.display = 'block';
      const perRuota = {};
      archivio.righe.forEach(r => {
        if (!perRuota[r.ruota]) perRuota[r.ruota] = { n: 0, min: r.data, max: r.data };
        perRuota[r.ruota].n++;
        if (parseData(r.data) < parseData(perRuota[r.ruota].min)) perRuota[r.ruota].min = r.data;
        if (parseData(r.data) > parseData(perRuota[r.ruota].max)) perRuota[r.ruota].max = r.data;
      });
      let html = `<div style="font-weight:600;color:#4fae7a;">✅ VALIDAZIONE OK</div>`;
      html += `<div style="margin-top:4px;">Righe: <b>${archivio.righe.length.toLocaleString('it-IT')}</b> · Ruote: <b>${Object.keys(perRuota).length}/11</b></div>`;
      const venus = archivio.righe.filter(r => parseData(r.data) >= parseData('2005-05-05')).length;
      const quattro = archivio.righe.filter(r => parseData(r.data) >= parseData('2023-07-07')).length;
      html += `<div style="margin-top:4px;color:#c9b98f;">Epoca Venus: ${venus.toLocaleString('it-IT')} righe · 4 Estrazioni: ${quattro.toLocaleString('it-IT')} righe</div>`;
      html += `<table><tr><th>Ruota</th><th>Estrazioni</th><th>Prima</th><th>Ultima</th></tr>`;
      for (const [ruota, s] of Object.entries(perRuota)) {
        html += `<tr><td>${ruota} ${CODICE_TO_NOME[ruota] || ''}</td><td>${s.n.toLocaleString('it-IT')}</td><td>${s.min}</td><td>${s.max}</td></tr>`;
      }
      html += `</table>`;
      box.innerHTML = html;
      logArchivio(`🔍 Validazione completata`, 'ok');
    }

    function filtraPerEpoca(righe) {
      const epocaKey = document.getElementById('epocaSelect').value;
      const epoca = EPOCHE[epocaKey];
      if (!epoca.dataInizio) return righe;
      const dInizio = parseData(epoca.dataInizio);
      return righe.filter(r => parseData(r.data) >= dInizio);
    }

    function portaEstrazioni(n) {
      if (!archivio) return;
      const ruota = document.getElementById('archivioRuotaSelect').value;
      let sub = archivio.righe.filter(r => r.ruota === ruota);
      sub = filtraPerEpoca(sub);
      sub.sort((a, b) => parseData(a.data) - parseData(b.data));
      if (!sub.length) { showNotification('️ Nessuna estrazione per questa ruota/epoca'); return; }
      const ultime = sub.slice(-n);
      drawHighlighted.clear();
      ultime.forEach(r => [r.n1, r.n2, r.n3, r.n4, r.n5].forEach(x => drawHighlighted.add(x)));
      renderDrawChips(); renderAll();
      analizzaCoerenzaEstrazioneUI([...drawHighlighted]);
      aggiornaCadenzeDecine();
      const dateStr = ultime.map(r => r.data).join(', ');
      showNotification(`🎯 ${CODICE_TO_NOME[ruota]}: ${ultime.length} estraz. (${dateStr})`, 3500);
      logArchivio(`🎯 Caricate ${ultime.length} estrazioni di ${ruota} sul Cerchio`, 'ok');
    }

    // ============================================================
    // STATISTICHE E PATTERN
    // ============================================================
    function analizzaFrequenze(ruota, ultimeN = 100) {
      if (!archivio) return null;
      const sub = archivio.righe.filter(r => r.ruota === ruota).sort((a, b) => parseData(b.data) - parseData(a.data)).slice(0, ultimeN);
      if (!sub.length) return null;
      const freq = new Array(91).fill(0);
      const ultimeUscite = new Array(91).fill(-1);
      sub.forEach((r, idx) => {
        [r.n1, r.n2, r.n3, r.n4, r.n5].forEach(n => {
          freq[n]++;
          if (ultimeUscite[n] === -1) ultimeUscite[n] = idx;
        });
      });
      const top10 = freq.map((f, i) => ({ n: i, f, ritardo: ultimeUscite[i] })).filter(x => x.n > 0).sort((a, b) => b.f - a.f).slice(0, 10);
      return { freq, ultimeUscite, top10, totale: sub.length };
    }

    function analizzaPattern(ruota, ultimeN = 200) {
      if (!archivio) return null;
      const sub = archivio.righe.filter(r => r.ruota === ruota).sort((a, b) => parseData(b.data) - parseData(a.data)).slice(0, ultimeN);
      if (!sub.length) return null;
      const coppie = new Map();
      const triple = new Map();
      sub.forEach(r => {
        const nums = [r.n1, r.n2, r.n3, r.n4, r.n5].sort((a, b) => a - b);
        for (let i = 0; i < nums.length; i++) {
          for (let j = i + 1; j < nums.length; j++) {
            const key = `${nums[i]}-${nums[j]}`;
            coppie.set(key, (coppie.get(key) || 0) + 1);
          }
        }
        for (let i = 0; i < nums.length; i++) {
          for (let j = i + 1; j < nums.length; j++) {
            for (let k = j + 1; k < nums.length; k++) {
              const key = `${nums[i]}-${nums[j]}-${nums[k]}`;
              triple.set(key, (triple.get(key) || 0) + 1);
            }
          }
        }
      });
      return {
        coppie: [...coppie.entries()].map(([c, f]) => ({ coppia: c, freq: f })).sort((a, b) => b.freq - a.freq).slice(0, 15),
        triple: [...triple.entries()].map(([c, f]) => ({ tripla: c, freq: f })).sort((a, b) => b.freq - a.freq).slice(0, 10),
        totale: sub.length
      };
    }

    function mostraStatisticheTop10() {
      const ruota = document.getElementById('statsRuota').value;
      const analisi = analizzaFrequenze(ruota, 100);
      if (!analisi) {
        document.getElementById('statsResults').innerHTML = '<div style="color:#8a7a5c;padding:8px;text-align:center;">Carica prima l\'archivio</div>';
        return;
      }
      let html = `<div style="color:var(--brass-hi);font-weight:600;margin:8px 0 6px;font-size:12px;">Top 10 numeri più frequenti (${analisi.totale} estrazioni):</div>`;
      html += `<div class="stats-grid">`;
      analisi.top10.forEach(t => {
        html += `<div class="stat-item" data-n="${t.n}">
          <div class="stat-num">${t.n}</div>
          <div class="stat-freq">${t.f} volte</div>
          <div class="stat-freq" style="font-size:9px;opacity:.7;">${t.ritardo === 0 ? 'ultimo' : 'ritardo ' + t.ritardo}</div>
        </div>`;
      });
      html += `</div>`;
      const box = document.getElementById('statsResults');
      box.innerHTML = html;
      box.querySelectorAll('.stat-item').forEach(el => {
        el.addEventListener('click', () => {
          const n = parseInt(el.dataset.n);
          highlighted.add(n);
          renderChips();
          renderAll();
          aggiornaCadenzeDecine();
          showNotification(` ${n} evidenziato sul Cerchio`);
        });
      });
      showNotification(` Statistiche ${CODICE_TO_NOME[ruota]} caricate`);
    }

    function mostraStatistichePattern() {
      const ruota = document.getElementById('statsRuota').value;
      const analisi = analizzaPattern(ruota, 200);
      if (!analisi) {
        document.getElementById('statsResults').innerHTML = '<div style="color:#8a7a5c;padding:8px;text-align:center;">Carica prima l\'archivio</div>';
        return;
      }
      let html = `<div style="color:var(--brass-hi);font-weight:600;margin:8px 0 6px;font-size:12px;">Top 15 coppie più frequenti (${analisi.totale} estrazioni):</div>`;
      html += `<div class="pattern-box">`;
      analisi.coppie.slice(0, 15).forEach(p => {
        html += `<div class="pattern-item" data-pair="${p.coppia}">
          <span class="pair">${p.coppia}</span>
          <span class="freq">${p.freq} volte</span>
        </div>`;
      });
      html += `</div>`;
      html += `<div style="color:var(--brass-hi);font-weight:600;margin:12px 0 6px;font-size:12px;">Top 10 triple più frequenti:</div>`;
      html += `<div class="pattern-box">`;
      analisi.triple.slice(0, 10).forEach(p => {
        html += `<div class="pattern-item" data-triple="${p.tripla}">
          <span class="pair">${p.tripla}</span>
          <span class="freq">${p.freq} volte</span>
        </div>`;
      });
      html += `</div>`;
      const box = document.getElementById('statsResults');
      box.innerHTML = html;
      box.querySelectorAll('.pattern-item').forEach(el => {
        el.addEventListener('click', () => {
          const nums = (el.dataset.pair || el.dataset.triple).split('-').map(Number);
          nums.forEach(n => highlighted.add(n));
          renderChips();
          renderAll();
          aggiornaCadenzeDecine();
          showNotification(`📊 ${nums.length} numeri evidenziati`);
        });
      });
      showNotification(`📊 Pattern ${CODICE_TO_NOME[ruota]} caricati`);
    }

    // ============================================================
    // METODO DELLE 3 ESTRAZIONI (MANNA)
    // ============================================================
    function runManna() {
      if (!archivio || !archivio.righe.length) {
        showNotification('⚠️ Carica prima l\'archivio');
        return;
      }
      const ruota = document.getElementById('mannaRuota').value;
      const sub = archivio.righe.filter(r => r.ruota === ruota).sort((a, b) => parseData(b.data) - parseData(a.data)).slice(0, 3);
      if (sub.length < 3) {
        showNotification('⚠️ Servono almeno 3 estrazioni');
        return;
      }
      const e1 = [sub[2].n1, sub[2].n2, sub[2].n3, sub[2].n4, sub[2].n5];
      const e2 = [sub[1].n1, sub[1].n2, sub[1].n3, sub[1].n4, sub[1].n5];
      const e3 = [sub[0].n1, sub[0].n2, sub[0].n3, sub[0].n4, sub[0].n5];
      const somme = [];
      for (let i = 0; i < 5; i++) {
        somme.push(fuori90(e1[i] + e2[i] + e3[i]));
      }
      const diff12 = [];
      const diff23 = [];
      for (let i = 0; i < 5; i++) {
        diff12.push(fuori90(Math.abs(e1[i] - e2[i])));
        diff23.push(fuori90(Math.abs(e2[i] - e3[i])));
      }
      const spie = [];
      for (let i = 0; i < 5; i++) {
        spie.push(fuori90(e1[i] + e3[i]));
      }
      MANNA_RESULTS = [...new Set([...somme, ...diff12, ...diff23, ...spie])].slice(0, 12);
      let html = `<div style="font-size:11px;color:#c9b98f;margin:8px 0;">Ultime 3 estrazioni di ${CODICE_TO_NOME[ruota]}:</div>`;
      html += `<div class="manna-grid">`;
      html += `<div class="manna-cell header">E3</div>`;
      e3.forEach(n => { html += `<div class="manna-cell">${n}</div>`; });
      html += `<div class="manna-cell header">E2</div>`;
      e2.forEach(n => { html += `<div class="manna-cell">${n}</div>`; });
      html += `<div class="manna-cell header">E1</div>`;
      e1.forEach(n => { html += `<div class="manna-cell">${n}</div>`; });
      html += `</div>`;
      html += `<div style="font-size:11px;color:#c9b98f;margin:8px 0 4px;">Somme verticali (Fuori 90):</div>`;
      html += `<div class="manna-grid">`;
      html += `<div class="manna-cell header">Somma</div>`;
      somme.forEach(n => { html += `<div class="manna-cell result">${n}</div>`; });
      html += `</div>`;
      html += `<div style="font-size:11px;color:#c9b98f;margin:8px 0 4px;">Differenze E1-E2 (Fuori 90):</div>`;
      html += `<div class="manna-grid">`;
      html += `<div class="manna-cell header">Diff</div>`;
      diff12.forEach(n => { html += `<div class="manna-cell diff">${n}</div>`; });
      html += `</div>`;
      html += `<div style="font-size:11px;color:#c9b98f;margin:8px 0 4px;">Spie incrociate E1+E3 (Fuori 90):</div>`;
      html += `<div class="manna-grid">`;
      html += `<div class="manna-cell header">Spie</div>`;
      spie.forEach(n => { html += `<div class="manna-cell result">${n}</div>`; });
      html += `</div>`;
      html += `<div class="btn-row" style="margin-top:10px;"><button class="step primary" id="mannaHighlight">🎯 Evidenzia previsioni</button></div>`;
      document.getElementById('mannaResults').innerHTML = html;
      document.getElementById('mannaHighlight').addEventListener('click', () => {
        MANNA_RESULTS.forEach(n => highlighted.add(n));
        renderChips();
        renderAll();
        aggiornaCadenzeDecine();
        showNotification(` ${MANNA_RESULTS.length} numeri Manna evidenziati`);
      });
      showNotification(` Metodo Manna calcolato per ${CODICE_TO_NOME[ruota]}`);
      renderNumeriPromettenti();
    }

    // ============================================================
    // PIRAMIDE CICLOTIMICA
    // ============================================================
    function runPiramide() {
      const base = getBase();
      if (base.length < 2) {
        showNotification('️ Carica prima almeno 2 numeri');
        return;
      }
      const nums = base.slice(0, 5).sort((a, b) => a - b);
      if (nums.length < 2) {
        showNotification('⚠️ Servono almeno 2 numeri');
        return;
      }
      let rows = [nums];
      while (rows[rows.length - 1].length > 1) {
        const prev = rows[rows.length - 1];
        const next = [];
        for (let i = 0; i < prev.length - 1; i++) {
          next.push(fuori90(Math.abs(prev[i] - prev[i + 1])));
        }
        rows.push(next);
      }
      const vertice = rows[rows.length - 1][0];
      PIRAMIDE_RESULTS = [vertice];
      let html = `<div class="piramide">`;
      rows.forEach((row, idx) => {
        html += `<div class="piramide-row">`;
        row.forEach(n => {
          const isVertice = idx === rows.length - 1;
          html += `<div class="piramide-num ${isVertice ? 'vertice' : ''}">${n}</div>`;
        });
        html += `</div>`;
      });
      html += `</div>`;
      html += `<div style="text-align:center;margin-top:10px;font-size:13px;color:#c9b98f;">Vertice previsto: <b style="color:#9b59b6;font-size:18px;">${vertice}</b></div>`;
      html += `<div class="btn-row" style="margin-top:10px;"><button class="step primary" id="piramideHighlight">🎯 Evidenzia vertice</button></div>`;
      document.getElementById('piramideResults').innerHTML = html;
      document.getElementById('piramideHighlight').addEventListener('click', () => {
        highlighted.add(vertice);
        renderChips();
        renderAll();
        aggiornaCadenzeDecine();
        showNotification(`🔺 Vertice ${vertice} evidenziato`);
      });
      showNotification(`🔺 Piramide generata con vertice ${vertice}`);
      renderNumeriPromettenti();
    }

    // ============================================================
    // SPIE PER RUOTA
    // ============================================================
    function runSpie() {
      if (!archivio || !archivio.righe.length) {
        showNotification('️ Carica prima l\'archivio');
        return;
      }
      const ruota = document.getElementById('spieRuota').value;
      const base = [...drawHighlighted, ...highlighted];
      if (base.length === 0) {
        showNotification('⚠️ Evidenzia prima almeno un numero');
        return;
      }
      const sub = archivio.righe.filter(r => r.ruota === ruota).sort((a, b) => parseData(a.data) - parseData(b.data));
      const spieMap = new Map();
      base.forEach(spia => {
        const risultati = new Map();
        for (let i = 0; i < sub.length - 1; i++) {
          const nums = [sub[i].n1, sub[i].n2, sub[i].n3, sub[i].n4, sub[i].n5];
          if (!nums.includes(spia)) continue;
          for (let j = 1; j <= 5 && i + j < sub.length; j++) {
            const nextNums = [sub[i + j].n1, sub[i + j].n2, sub[i + j].n3, sub[i + j].n4, sub[i + j].n5];
            nextNums.forEach(n => {
              risultati.set(n, (risultati.get(n) || 0) + 1);
            });
          }
        }
        const usciteSpia = sub.filter(r => [r.n1, r.n2, r.n3, r.n4, r.n5].includes(spia)).length;
        if (usciteSpia > 0 && risultati.size > 0) {
          const top3 = [...risultati.entries()]
            .map(([n, count]) => ({ n, count, pct: (count / (usciteSpia * 5) * 100) }))
            .sort((a, b) => b.count - a.count)
            .slice(0, 3);
          spieMap.set(spia, { uscite: usciteSpia, top3 });
        }
      });
      let html = '';
      if (spieMap.size === 0) {
        html = '<div style="color:#8a7a5c;padding:8px;text-align:center;">Nessuna spia trovata nell\'archivio</div>';
      } else {
        spieMap.forEach((data, spia) => {
          html += `<div class="spia-card">`;
          html += `<div class="spia-header"><div class="spia-num">${spia}</div><div style="font-size:11px;color:#c9b98f;">Uscita ${data.uscite} volte · Top 3 annunciati:</div></div>`;
          html += `<div class="spia-results">`;
          data.top3.forEach(r => {
            html += `<div class="spia-item" data-n="${r.n}">
              <div class="num">${r.n}</div>
              <div class="pct">${r.pct.toFixed(0)}%</div>
            </div>`;
          });
          html += `</div></div>`;
        });
      }
      document.getElementById('spieResults').innerHTML = html;
      document.querySelectorAll('.spia-item').forEach(el => {
        el.addEventListener('click', () => {
          const n = parseInt(el.dataset.n);
          highlighted.add(n);
          renderChips();
          renderAll();
          aggiornaCadenzeDecine();
          showNotification(`️ ${n} evidenziato come spia`);
        });
      });
      showNotification(`️ Analizzate ${spieMap.size} spie su ${CODICE_TO_NOME[ruota]}`);
    }

    // ============================================================
    // FIGURE MANCANTI (RITARDO FIGURE)
    // ============================================================
    function runFigureDelay() {
      if (!archivio || !archivio.righe.length) {
        showNotification('⚠️ Carica prima l\'archivio');
        return;
      }
      const ruota = document.getElementById('figDelayRuota').value;
      const sub = archivio.righe.filter(r => r.ruota === ruota).sort((a, b) => parseData(b.data) - parseData(a.data)).slice(0, 100);
      if (sub.length < 10) {
        showNotification('️ Servono almeno 10 estrazioni');
        return;
      }
      const figureDelay = {
        'Opposti 180°': { ritardo: 0, trovato: false },
        'Triangolo 120°': { ritardo: 0, trovato: false },
        'Terzina 60°': { ritardo: 0, trovato: false },
        'Quadrato 90°': { ritardo: 0, trovato: false }
      };
      sub.forEach((r, idx) => {
        const nums = [r.n1, r.n2, r.n3, r.n4, r.n5];
        const figs = detectBaseFigures(nums);
        figs.forEach(f => {
          if (!figureDelay[f.type].trovato) {
            figureDelay[f.type].ritardo = idx;
            figureDelay[f.type].trovato = true;
          }
        });
      });
      const figs = Object.entries(figureDelay).map(([nome, data]) => ({
        nome,
        ritardo: data.trovato ? data.ritardo : sub.length,
        alert: (data.trovato ? data.ritardo : sub.length) > 15
      })).sort((a, b) => b.ritardo - a.ritardo);
      let html = `<div class="figure-delay">`;
      figs.forEach(f => {
        html += `<div class="fig-delay-item ${f.alert ? 'alert' : ''}">
          <span class="fname">${f.nome}</span>
          <span class="fdelay">${f.ritardo === sub.length ? 'Mai' : f.ritardo + ' estr.'}</span>
        </div>`;
      });
      html += `</div>`;
      html += `<div style="font-size:10px;color:#8a7a5c;margin-top:8px;text-align:center;">Analisi ultime 100 estrazioni · Figure con ritardo >15 sono evidenziate</div>`;
      document.getElementById('figDelayResults').innerHTML = html;
      showNotification(`⏰ Ritardo figure calcolato per ${CODICE_TO_NOME[ruota]}`);
    }

    // ============================================================
    // CADENZE E DECINE
    // ============================================================
    function aggiornaCadenzeDecine() {
      const nums = [...highlighted, ...drawHighlighted];
      const box = document.getElementById('cadenzeResults');
      if (nums.length < 2) {
        box.innerHTML = '<div style="text-align:center;color:#8a7a5c;font-size:12px;padding:8px;">Evidenzia almeno 2 numeri per vedere l\'analisi</div>';
        return;
      }
      const cadenze = new Array(10).fill(0);
      nums.forEach(n => { cadenze[n % 10]++; });
      const decine = new Array(9).fill(0);
      nums.forEach(n => { decine[Math.floor((n - 1) / 10)]++; });
      let html = `<div style="font-size:11px;color:#c9b98f;margin:8px 0 4px;">Distribuzione per Cadenza (cifra finale):</div>`;
      html += `<div class="cadenze-grid">`;
      for (let i = 0; i < 10; i++) {
        const cls = cadenze[i] === 0 ? 'cold' : (cadenze[i] >= 2 ? 'hot' : '');
        html += `<div class="cadenza-cell ${cls}">
          <div class="num">${i}</div>
          <div class="count">${cadenze[i]}</div>
        </div>`;
      }
      html += `</div>`;
      html += `<div style="font-size:11px;color:#c9b98f;margin:12px 0 4px;">Distribuzione per Decina:</div>`;
      html += `<div class="cadenze-grid" style="grid-template-columns:repeat(9,1fr);">`;
      const decLabels = ['1-10', '11-20', '21-30', '31-40', '41-50', '51-60', '61-70', '71-80', '81-90'];
      for (let i = 0; i < 9; i++) {
        const cls = decine[i] === 0 ? 'cold' : (decine[i] >= 2 ? 'hot' : '');
        html += `<div class="cadenza-cell ${cls}">
          <div class="num" style="font-size:11px;">${decLabels[i]}</div>
          <div class="count">${decine[i]}</div>
        </div>`;
      }
      html += `</div>`;
      html += `<div style="font-size:10px;color:#8a7a5c;margin-top:8px;line-height:1.5;">
        <span style="color:#e0a0a0;">■</span> Fredde (assenti) · <span style="color:#4fae7a;">■</span> Calde (≥2 numeri)
      </div>`;
      box.innerHTML = html;
    }

    // ============================================================
    // ANALISI COERENZA GEOMETRICA
    // ============================================================
    function analizzaCoerenzaEstrazione(numeri) {
      if (numeri.length < 2) return { score: 0, dettagli: [] };
      const dettagli = [];
      let score = 0;
      const figs = detectBaseFigures(numeri);
      figs.forEach(f => {
        score += f.type === 'Opposti' ? 3 : f.type === 'Triangolo' ? 4 : f.type === 'Quadrato' ? 5 : 2;
        dettagli.push({ tipo: 'figura', testo: `✓ ${f.type}: ${f.numbers.join('-')}` });
      });
      for (let i = 0; i < numeri.length; i++) {
        for (let j = i + 1; j < numeri.length; j++) {
          const diff = Math.abs(numeri[i] - numeri[j]);
          if (diff === 45) {
            score += 2;
            dettagli.push({ tipo: 'distanza', testo: `✓ Opposti (Δ45): ${numeri[i]}-${numeri[j]}` });
          } else if (diff === 30 || diff === 60) {
            score += 1.5;
            dettagli.push({ tipo: 'distanza', testo: `✓ Trigono (Δ${diff}): ${numeri[i]}-${numeri[j]}` });
          } else if (diff === 15 || diff === 75) {
            score += 1;
            dettagli.push({ tipo: 'distanza', testo: `✓ Sesto (Δ${diff}): ${numeri[i]}-${numeri[j]}` });
          }
        }
      }
      numeri.forEach(n => {
        const ds = String(n).split('').reduce((s, d) => s + +d, 0);
        const ridotto = ds > 9 ? ds - 9 : ds;
        if (numeri.includes(ridotto) && ridotto !== n) {
          score += 0.5;
          dettagli.push({ tipo: 'somma', testo: `✓ Somma cifre: ${n} → ${ridotto}` });
        }
      });
      return { score, dettagli: dettagli.slice(0, 10) };
    }

    function analizzaCoerenzaEstrazioneUI(numeri) {
      const analisi = analizzaCoerenzaEstrazione(numeri);
      const box = document.getElementById('coerenzaBox');
      if (!analisi.dettagli.length) { box.innerHTML = ''; return; }
      let html = `<div class="coerenza-box">`;
      html += `<div style="color:var(--brass-hi);font-weight:600;margin-bottom:6px;">Coerenza geometrica: <span style="color:#4fae7a;">${analisi.score.toFixed(1)}</span></div>`;
      analisi.dettagli.forEach(d => { html += `<div class="coerenza-item">${d.testo}</div>`; });
      html += `</div>`;
      box.innerHTML = html;
    }

    // ============================================================
    // VERIFICA A RITROSO (BACKTEST)
    // ============================================================
    function popolaBtRuote() {
      document.getElementById('btRuotaSelect').innerHTML = Object.entries(RUOTE_NOME).map(([k, v]) => `<option value="${k}">${k} - ${v}</option>`).join('');
    }

    function setupBtDateRange() {
      const inp = document.getElementById('btDateInput');
      if (archivio && archivio.righe.length) {
        const dates = archivio.righe.map(r => parseData(r.data)).filter(Boolean).sort((a, b) => a - b);
        inp.min = toISOData(dates[0]);
        inp.max = toISOData(dates[dates.length - 1]);
        if (!inp.value) inp.value = toISOData(dates[dates.length - 1]);
      } else {
        if (!inp.value) inp.value = toISOData(new Date());
      }
    }

    function computeBacktestCandidates(baseEstrazioni) {
      const n = baseEstrazioni.length;
      if (!n) return [];
      const lastSeen = {};
      const freq = new Array(91).fill(0);
      baseEstrazioni.forEach((r, idx) => {
        [r.n1, r.n2, r.n3, r.n4, r.n5].forEach(x => { lastSeen[x] = idx; freq[x]++; });
      });
      const lastDraw = baseEstrazioni[n - 1];
      const lastNums = [lastDraw.n1, lastDraw.n2, lastDraw.n3, lastDraw.n4, lastDraw.n5];
      const scores = [];
      for (let num = 1; num <= 90; num++) {
        const delay = (lastSeen[num] !== undefined) ? (n - 1 - lastSeen[num]) : n;
        const frequenza = freq[num] / n;
        let bonus = 0;
        const motivi = [];
        lastNums.forEach(ln => {
          if (ln === num) return;
          if (rot(ln, 45) === num) { bonus += 15; motivi.push(`opposto ${ln}`); }
          else if (rot(ln, 30) === num) { bonus += 8; motivi.push(`trigono ${ln}`); }
          else if (rot(ln, 60) === num) { bonus += 8; motivi.push(`trigono ${ln}`); }
          else if (rot(ln, 15) === num) { bonus += 5; motivi.push(`sesto ${ln}`); }
        });
        if (frequenza > 0.15) { bonus += 5; motivi.push('frequente'); }
        const scoreFinale = delay + bonus + (frequenza * 10);
        scores.push({ n: num, delay, score: scoreFinale, frequenza, motivi });
      }
      scores.sort((a, b) => b.score - a.score || b.delay - a.delay);
      return scores;
    }

    function runBacktest() {
      if (!archivio || !archivio.righe.length) { showNotification('⚠️ Carica prima l\'archivio (Carica ZIP Locale)'); return; }
      const ruota = document.getElementById('btRuotaSelect').value;
      const dateStr = document.getElementById('btDateInput').value;
      if (!dateStr) { showNotification('⚠️ Seleziona una data dal calendario'); return; }
      const dSel = parseData(dateStr);
      const nBase = parseInt(document.getElementById('btNumBase').value) || 60;
      const topN = parseInt(document.getElementById('btTopN').value) || 10;
      const sub = archivio.righe.filter(r => r.ruota === ruota).sort((a, b) => parseData(a.data) - parseData(b.data));
      if (!sub.length) { showNotification('️ Nessuna estrazione per questa ruota'); return; }
      const upTo = sub.filter(r => parseData(r.data) <= dSel);
      if (upTo.length < 5) { showNotification('⚠️ Troppo poche estrazioni fino a questa data'); return; }
      const base = upTo.slice(-nBase);
      const exact = sub.find(r => parseData(r.data).getTime() === dSel.getTime());
      const precedente = upTo[upTo.length - 1];
      const after = sub.filter(r => parseData(r.data) > dSel);
      const nextDraw = after.length ? after[0] : null;
      const candidates = computeBacktestCandidates(base);
      const topCand = candidates.slice(0, topN);
      LAST_BACKTEST = topCand;
      renderBacktestResult({
        ruota, dSel, base, exact, precedente, nextDraw, topCand,
        precISO: precedente ? toISOData(parseData(precedente.data)) : '',
        nextISO: nextDraw ? toISOData(parseData(nextDraw.data)) : ''
      });
      renderNumeriPromettenti();
    }

    function renderBacktestResult(d) {
      const box = document.getElementById('btResults');
      const nome = CODICE_TO_NOME[d.ruota];
      const actualNums = d.nextDraw ? [d.nextDraw.n1, d.nextDraw.n2, d.nextDraw.n3, d.nextDraw.n4, d.nextDraw.n5] : [];
      const topNums = d.topCand.map(c => c.n);
      const hits = actualNums.filter(n => topNums.includes(n));
      let html = `<div class="bt-result">`;
      html += `<div style="color:var(--brass-hi);font-weight:600;margin-bottom:4px;">📊 ${nome} (${d.ruota}) — verifica al ${d.dSel.toLocaleDateString('it-IT')}</div>`;
      html += `<div style="color:#c9b98f;">Base: ${d.base.length} estrazioni (${d.base[0].data} → ${d.base[d.base.length - 1].data})</div>`;
      if (d.exact) {
        html += `<div class="bt-label">Estrazione esatta del giorno ${d.exact.data}:</div><div class="bt-row">`;
        [d.exact.n1, d.exact.n2, d.exact.n3, d.exact.n4, d.exact.n5].forEach(n => { html += `<span class="bt-num actual">${n}</span>`; });
        html += `</div>`;
      } else {
        html += `<div style="margin-top:8px;padding:8px;background:rgba(224,176,74,.15);border-radius:6px;color:#e0b04a;font-size:11.5px;line-height:1.6;">
          ⚠️ <b>Nessuna estrazione in data ${d.dSel.toLocaleDateString('it-IT')}</b> per ${nome}.<br>
          Ultima precedente usata come base: <b>${d.precedente.data}</b><br>
          <span class="bt-jump" data-data="${d.precISO}">◀ vai alla data precedente</span> ·
          ${d.nextISO ? `<span class="bt-jump" data-data="${d.nextISO}">vai alla successiva ▶</span>` : ''}
        </div>`;
      }
      html += `<div class="bt-label">Top ${d.topCand.length} numeri previsti (ritardatari + figure + frequenza):</div><div class="bt-row">`;
      d.topCand.forEach(c => {
        const isHit = actualNums.includes(c.n);
        const title = `ritardo ${c.delay} · freq ${(c.frequenza * 100).toFixed(1)}%${c.motivi.length ? ` · ${c.motivi.join(', ')}` : ''}`;
        html += `<span class="bt-num ${isHit ? 'hit' : ''}" title="${title}">${c.n}</span>`;
      });
      html += `</div>`;
      if (d.nextDraw) {
        html += `<div class="bt-label">Estrazione reale successiva (${d.nextDraw.data}):</div><div class="bt-row">`;
        actualNums.forEach(n => {
          const isHit = topNums.includes(n);
          html += `<span class="bt-num actual" style="${isHit ? 'background:var(--gold-fill);color:#1a1206;border:2px solid #4fae7a;' : ''}">${n}</span>`;
        });
        html += `</div>`;
        const pct = ((hits.length / 5) * 100).toFixed(0);
        html += `<div style="margin-top:8px;font-weight:600;color:${hits.length ? '#4fae7a' : '#e0a0a0'};">`;
        html += `🎯 Match: ${hits.length}/5 numeri previsti (${pct}%)${hits.length ? ` → ${hits.join(', ')}` : ''}</div>`;
      } else {
        html += `<div style="margin-top:8px;color:#e0b04a;">ℹ️ Nessuna estrazione successiva disponibile (data troppo recente)</div>`;
      }
      html += `<div class="btn-row" style="margin-top:10px;">`;
      html += `<button class="step" id="btBringCand"> Porta previsti sul Cerchio</button>`;
      if (d.nextDraw) html += `<button class="step" id="btBringActual"> Porta estrazione reale</button>`;
      html += `</div></div>`;
      box.innerHTML = html;
      box.querySelectorAll('.bt-jump').forEach(s => {
        s.addEventListener('click', () => { document.getElementById('btDateInput').value = s.dataset.data; runBacktest(); });
      });
      document.getElementById('btBringCand').addEventListener('click', () => {
        drawHighlighted.clear(); topNums.forEach(n => drawHighlighted.add(n));
        renderDrawChips(); renderAll(); aggiornaCadenzeDecine(); showNotification(`🎯 ${topNums.length} numeri previsti sul Cerchio`);
      });
      if (d.nextDraw) {
        document.getElementById('btBringActual').addEventListener('click', () => {
          drawHighlighted.clear(); actualNums.forEach(n => drawHighlighted.add(n));
          renderDrawChips(); renderAll(); aggiornaCadenzeDecine(); showNotification(' Estrazione reale sul Cerchio');
        });
      }
    }

    // ============================================================
    // NUMERI PROMETTENTI
    // ============================================================
    function calcolaNumeriPromettenti() {
      const mappa = new Map();
      const addFonte = (n, score, fonte, peso = 1) => {
        n = fuori90(n);
        if (!mappa.has(n)) mappa.set(n, { n, score: 0, fonti: new Map(), dettagli: {} });
        const e = mappa.get(n);
        e.score += score * peso;
        const count = e.fonti.get(fonte) || 0;
        e.fonti.set(fonte, count + 1);
      };
      (ENGINE.candidates || []).slice(0, 15).forEach(c => { addFonte(c.n, Math.min(100, c.score), 'motore', 1.0); });
      (CONVERGENCE || []).slice(0, 15).forEach(c => {
        const peso = Math.min(1.5, 1 + (c.drawCount - 2) * 0.1);
        addFonte(c.n, Math.min(100, c.score), 'conv', peso);
      });
      (LAST_BACKTEST || []).slice(0, 15).forEach((c, i) => {
        const peso = 1.2 - (i * 0.05);
        addFonte(c.n, Math.min(100, c.score), 'backtest', Math.max(0.5, peso));
      });
      MANNA_RESULTS.slice(0, 12).forEach((n, i) => {
        const peso = 1.3 - (i * 0.05);
        addFonte(n, 80 - i * 5, 'manna', Math.max(0.5, peso));
      });
      PIRAMIDE_RESULTS.forEach(n => { addFonte(n, 90, 'piramide', 1.2); });
      return [...mappa.values()]
        .map(e => ({ ...e, scoreFinale: e.score * (1 + (e.fonti.size - 1) * 0.25) }))
        .sort((a, b) => b.scoreFinale - a.scoreFinale || b.fonti.size - a.fonti.size || a.n - b.n)
        .slice(0, 12);
    }

    function renderNumeriPromettenti() {
      const box = document.getElementById('esitiContent');
      const risultati = calcolaNumeriPromettenti();
      if (!risultati.length) {
        box.innerHTML = `<div class="esiti-vuoto">Esegui almeno un'analisi (⚡ Motore, 🔀 Convergenze, 🕰️ Backtest, 🎲 Manna o 🔺 Piramide)<br>poi premi <b>↻ Aggiorna</b>.</div>`;
        return;
      }
      const maxScore = risultati[0].scoreFinale || 1;
      box.innerHTML = `
        <div class="esiti-grid">
          ${risultati.map((r, i) => `
            <div class="esito-item">
              <div class="esito-num ${i === 0 ? 'top1' : ''}" data-n="${r.n}"
                   title="Score aggregato ${r.scoreFinale.toFixed(1)} — Fonti: ${[...r.fonti.keys()].join(', ')}">
                <span class="n">${r.n}</span>
                <span class="s">${(r.scoreFinale / maxScore * 100).toFixed(0)}</span>
              </div>
              <div class="esito-fonti">
                ${r.fonti.has('motore') ? '<span class="fonte-dot fd-motore"  title="Motore Previsivo"></span>' : ''}
                ${r.fonti.has('conv') ? '<span class="fonte-dot fd-conv"    title="Convergenze"></span>' : ''}
                ${r.fonti.has('backtest') ? '<span class="fonte-dot fd-backtest" title="Verifica a Ritroso"></span>' : ''}
                ${r.fonti.has('manna') ? '<span class="fonte-dot fd-manna"   title="Metodo Manna"></span>' : ''}
                ${r.fonti.has('piramide') ? '<span class="fonte-dot fd-piramide" title="Piramide"></span>' : ''}
              </div>
            </div>`).join('')}
        </div>
        <div class="esiti-legenda">
          <span><span class="fonte-dot fd-motore"></span>Motore</span>
          <span><span class="fonte-dot fd-conv"></span>Conv</span>
          <span><span class="fonte-dot fd-backtest"></span>Backtest</span>
          <span><span class="fonte-dot fd-manna"></span>Manna</span>
          <span><span class="fonte-dot fd-piramide"></span>Piramide</span>
        </div>`;
      box.querySelectorAll('.esito-num').forEach(el => {
        el.addEventListener('click', () => {
          const n = parseInt(el.dataset.n);
          highlighted.add(n); renderChips(); renderAll(); aggiornaCadenzeDecine();
          showNotification(`🎯 ${n} evidenziato sul Cerchio`);
        });
      });
    }

    // ============================================================
    // CERCHIO DI SPILIMBERGO
    // ============================================================
    const CX = 350, CY = 350, SECTORS = 32, SECTOR_ANGLE = 360 / SECTORS, HALF = SECTOR_ANGLE / 2;
    const REST_OFFSET = ((-HALF) % 360 + 360) % 360;
    const RINGS = [
      { key: 'r0', numbers: [1, 24, 47, 70, 3, 26, 49, 72, 5, 28, 51, 74, 7, 30, 53, 76, 9, 32, 55, 78, 11, 34, 57, 80, 13, 36, 59, 82, 15, 38], rIn: 76, rOut: 150, fs: 16 },
      { key: 'r1', numbers: [61, 84, 17, 40, 63, 86, 19, 42, 65, 88, 21, 44, 67, 90, 23, 46, 69, 2, 25, 48, 71, 4, 27, 50, 73, 6, 29, 52, 75, 8], rIn: 158, rOut: 222, fs: 18 },
      { key: 'r2', numbers: [31, 54, 77, 10, 33, 56, 79, 12, 35, 58, 81, 14, 37, 60, 83, 16, 39, 62, 85, 18, 41, 64, 87, 20, 43, 66, 89, 22, 45, 68], rIn: 230, rOut: 284, fs: 20 }
    ];
    const offset = [REST_OFFSET, REST_OFFSET, REST_OFFSET];
    const highlighted = new Set(), drawHighlighted = new Set();
    const svg = document.getElementById('wheel');
    const NS = 'http://www.w3.org/2000/svg';
    const ENGINE = { figures: [], candidates: [], base: [], firma: [], figDin: [], activeDist: null };
    let overlayG = null, heatOn = false, heatMap = new Map(), tourTimer = null;
    const HISTORY = [];
    let CONVERGENCE = [];

    function rot(n, step) { return ((n - 1 + step) % 90) + 1; }

    function showNotification(msg, dur = 2500) {
      const n = document.getElementById('notification');
      n.textContent = msg; n.classList.add('show');
      setTimeout(() => n.classList.remove('show'), dur);
    }

    function el(tag, attrs) {
      const e = document.createElementNS(NS, tag);
      for (const k in attrs) e.setAttribute(k, attrs[k]);
      return e;
    }

    function normalizeDeg(d) { d = d % 360; if (d < 0) d += 360; return d; }

    function polar(cx, cy, r, ang) {
      const a = (ang - 90) * Math.PI / 180;
      return [cx + r * Math.cos(a), cy + r * Math.sin(a)];
    }

    function annulusPath(cx, cy, rin, rout) {
      return `M ${cx - rout} ${cy} A ${rout} ${rout} 0 1 0 ${cx + rout} ${cy} A ${rout} ${rout} 0 1 0 ${cx - rout} ${cy} Z M ${cx - rin} ${cy} A ${rin} ${rin} 0 1 1 ${cx + rin} ${cy} A ${rin} ${rin} 0 1 1 ${cx - rin} ${cy} Z`;
    }

    function buildStatic() {
      const grad = el('radialGradient', { id: 'paperGrad', cx: '45%', cy: '40%', r: '70%' });
      grad.appendChild(el('stop', { offset: '0%', 'stop-color': '#f8f1de' }));
      grad.appendChild(el('stop', { offset: '75%', 'stop-color': '#efe3c4' }));
      grad.appendChild(el('stop', { offset: '100%', 'stop-color': '#ddceac' }));
      const defs = el('defs', {}); defs.appendChild(grad); svg.appendChild(defs);
      svg.appendChild(el('circle', { cx: CX, cy: CY, r: 332, fill: 'url(#paperGrad)', stroke: 'var(--brass)', 'stroke-width': 3 }));
      svg.appendChild(el('circle', { cx: CX, cy: CY, r: 326, fill: 'none', stroke: 'var(--brass)', 'stroke-width': 1, opacity: .6 }));
      const dSize = 46;
      svg.appendChild(el('path', { d: `M ${CX} ${CY - dSize} L ${CX + dSize} ${CY} L ${CX} ${CY + dSize} L ${CX - dSize} ${CY} Z`, fill: '#00000007', stroke: 'var(--brass)', 'stroke-width': 1.3, opacity: .75 }));
      svg.appendChild(el('circle', { cx: CX, cy: CY, r: 3, fill: 'var(--ink)' }));
    }

    const COMPASS_DEFS = [['N', HALF], ['E', 90 + HALF], ['S', 180 + HALF], ['O', 270 + HALF]];

    function buildCompass(ring2) {
      const compassTexts = [];
      COMPASS_DEFS.forEach(([label, ang]) => {
        const [x1, y1] = polar(CX, CY, ring2.ring.rOut + 12, ang);
        const [x2, y2] = polar(CX, CY, ring2.ring.rOut + 26, ang);
        ring2.g.appendChild(el('line', { x1, y1, x2, y2, stroke: 'var(--ink)', 'stroke-width': 2.5 }));
        const t = el('text', { class: 'compass-label' }); t.textContent = label;
        ring2.textG.appendChild(t);
        compassTexts.push({ el: t, ang });
      });
      ring2.compassTexts = compassTexts;
    }

    const ringGroups = [];

    function buildRing(ring, idx) {
      const g = el('g', { id: 'lines-' + idx });
      g.appendChild(el('circle', { cx: CX, cy: CY, r: ring.rIn, class: 'band' }));
      g.appendChild(el('circle', { cx: CX, cy: CY, r: ring.rOut, class: 'band' }));
      for (let s = 0; s < SECTORS; s++) {
        const ang = s * SECTOR_ANGLE;
        const [x1, y1] = polar(CX, CY, ring.rIn, ang);
        const [x2, y2] = polar(CX, CY, ring.rOut, ang);
        g.appendChild(el('line', { x1, y1, x2, y2, class: 'divider' }));
      }
      for (let s = 30; s < 32; s++) {
        const a1 = s * SECTOR_ANGLE, a2 = (s + 1) * SECTOR_ANGLE;
        const [ox1, oy1] = polar(CX, CY, ring.rOut, a1), [ox2, oy2] = polar(CX, CY, ring.rOut, a2);
        const [ix2, iy2] = polar(CX, CY, ring.rIn, a2), [ix1, iy1] = polar(CX, CY, ring.rIn, a1);
        g.appendChild(el('path', { d: `M ${ox1} ${oy1} A ${ring.rOut} ${ring.rOut} 0 0 1 ${ox2} ${oy2} L ${ix2} ${iy2} A ${ring.rIn} ${ring.rIn} 0 0 0 ${ix1} ${iy1} Z`, class: 'gap-mark' }));
      }
      svg.appendChild(g);
      const textG = el('g', { id: 'text-' + idx });
      const dots = [], drawDots = [];
      const texts = ring.numbers.map(n => {
        const ddot = el('circle', { r: 18, class: 'draw-dot' }); textG.appendChild(ddot); drawDots.push(ddot);
        const dot = el('circle', { r: 13, class: 'highlight-dot' }); textG.appendChild(dot); dots.push(dot);
        const t = el('text', { class: 'num', 'font-size': ring.fs, 'data-number': n, 'data-ring': idx });
        t.textContent = n; textG.appendChild(t);
        return t;
      });
      svg.appendChild(textG);
      const hit = el('path', { class: 'ring-track', 'data-ring': idx, fill: '#fff', opacity: 0 });
      hit.setAttribute('d', annulusPath(CX, CY, ring.rIn - 4, ring.rOut + 4));
      hit.style.pointerEvents = 'fill'; svg.appendChild(hit);
      ringGroups.push({ g, textG, texts, dots, drawDots, hit, ring, idx });
    }

    function renderRing(idx) {
      const rg = ringGroups[idx];
      rg.g.setAttribute('transform', `rotate(${offset[idx]} ${CX} ${CY})`);
      const rMid = (rg.ring.rIn + rg.ring.rOut) / 2;
      rg.texts.forEach((t, i) => {
        const ang = (i + 0.5) * SECTOR_ANGLE + offset[idx];
        const [x, y] = polar(CX, CY, rMid, ang);
        t.setAttribute('x', x); t.setAttribute('y', y);
        const num = rg.ring.numbers[i];
        t.classList.toggle('highlighted', highlighted.has(num));
        t.classList.toggle('draw-hit', drawHighlighted.has(num));
        const dot = rg.dots[i]; dot.setAttribute('cx', x); dot.setAttribute('cy', y);
        dot.style.opacity = highlighted.has(num) ? 1 : 0;
        const ddot = rg.drawDots[i]; ddot.setAttribute('cx', x); ddot.setAttribute('cy', y);
        ddot.style.opacity = drawHighlighted.has(num) ? 1 : 0;
      });
      if (rg.compassTexts) rg.compassTexts.forEach(ct => {
        const [x, y] = polar(CX, CY, rg.ring.rOut + 34, ct.ang + offset[idx]);
        ct.el.setAttribute('x', x); ct.el.setAttribute('y', y);
      });
      updateReadout(); updateDistancePanel(); updateFigureList(); drawOppositeLines(); drawFigureOverlay(); applyHeat();
    }

    function compassAngles() {
      const n = normalizeDeg(offset[2] + HALF);
      return { N: n, E: normalizeDeg(n + 90), S: normalizeDeg(n + 180), O: normalizeDeg(n + 270) };
    }

    function sectorAt(ringIdx, compassAngle) {
      const rel = normalizeDeg(compassAngle - offset[ringIdx]);
      let idx = Math.round(rel / SECTOR_ANGLE - 0.5);
      return ((idx % SECTORS) + SECTORS) % SECTORS;
    }

    function valueAt(ringIdx, compassAngle) {
      const idx = sectorAt(ringIdx, compassAngle);
      const nums = RINGS[ringIdx].numbers;
      return idx < nums.length ? nums[idx] : null;
    }

    function updateReadout() {
      const dirs = compassAngles();
      for (let r = 0; r < 3; r++) for (const d in dirs) {
        const v = valueAt(r, dirs[d]);
        const cell = document.getElementById(`r${r}-${d}`);
        if (v === null) { cell.textContent = '—'; cell.classList.add('blank'); }
        else { cell.textContent = v; cell.classList.remove('blank'); }
      }
    }

    function snap(idx) {
      offset[idx] = normalizeDeg(Math.round((offset[idx] - HALF) / SECTOR_ANGLE) * SECTOR_ANGLE + HALF);
      renderRing(idx);
    }

    function stepRing(idx, dir) {
      offset[idx] = normalizeDeg(offset[idx] + dir * SECTOR_ANGLE);
      renderRing(idx);
    }

    let dragging = null;

    function angleFromEvent(evt) {
      const pt = svg.createSVGPoint(); pt.x = evt.clientX; pt.y = evt.clientY;
      const loc = pt.matrixTransform(svg.getScreenCTM().inverse());
      return { ang: Math.atan2(loc.y - CY, loc.x - CX) * 180 / Math.PI };
    }

    svg.addEventListener('pointerdown', e => {
      const target = e.target;
      if (!target.classList.contains('ring-track')) return;
      const idx = parseInt(target.getAttribute('data-ring'));
      const p = angleFromEvent(e);
      dragging = { idx, startAngle: p.ang, startOffset: offset[idx], moved: false, startClientX: e.clientX, startClientY: e.clientY };
      target.classList.add('dragging');
      svg.setPointerCapture(e.pointerId);
      e.preventDefault();
    });

    svg.addEventListener('pointermove', e => {
      if (!dragging) return;
      if (Math.hypot(e.clientX - dragging.startClientX, e.clientY - dragging.startClientY) > 4) dragging.moved = true;
      const p = angleFromEvent(e);
      offset[dragging.idx] = dragging.startOffset + (p.ang - dragging.startAngle);
      renderRing(dragging.idx);
    });

    function endDrag(e) {
      if (!dragging) return;
      const idx = dragging.idx, wasDrag = dragging.moved;
      document.querySelectorAll('.ring-track').forEach(t => t.classList.remove('dragging'));
      if (wasDrag) snap(idx);
      else {
        const p = angleFromEvent(e);
        const sIdx = sectorAt(idx, normalizeDeg(p.ang + 90));
        const nums = RINGS[idx].numbers;
        if (sIdx < nums.length) toggleHighlight(nums[sIdx]);
        renderRing(idx);
      }
      dragging = null;
    }

    svg.addEventListener('pointerup', endDrag);
    svg.addEventListener('pointercancel', endDrag);

    function calculateDistances() {
      if (highlighted.size < 2) return [];
      const nums = [...highlighted].sort((a, b) => a - b);
      const res = [];
      for (let i = 0; i < nums.length; i++) for (let j = i + 1; j < nums.length; j++) {
        const diff = Math.abs(nums[i] - nums[j]);
        res.push({ pair: `${nums[i]}-${nums[j]}`, dist: diff, comp: 90 - diff });
      }
      return res;
    }

    function updateDistancePanel() {
      const list = document.getElementById('distanceList');
      const d = calculateDistances();
      if (!d.length) { list.innerHTML = '<div style="text-align:center;color:#8a7a5c;font-size:12px;padding:8px;">Evidenzia almeno 2 numeri</div>'; return; }
      list.innerHTML = d.map(x => `<div class="distance-item"><span style="color:var(--brass-hi);font-weight:600;">${x.pair}</span><span style="color:#c9b98f;">Δ ${x.dist}</span><span style="color:#b3a37e;font-style:italic;">C ${x.comp}</span></div>`).join('');
    }

    function detectBaseFigures(nums) {
      const figs = [], S = new Set(nums);
      nums.forEach(n => {
        const o = rot(n, 45);
        if (S.has(o) && n < o) figs.push({ type: 'Opposti', numbers: [n, o], color: '#c0392b' });
        const t1 = rot(n, 30), t2 = rot(n, 60);
        if (S.has(t1) && S.has(t2) && n < t1 && t1 < t2) figs.push({ type: 'Triangolo', numbers: [n, t1, t2], color: '#2e8b57' });
        const e1 = rot(n, 15), e2 = rot(n, 30);
        if (S.has(e1) && S.has(e2) && n < e1 && e1 < e2) figs.push({ type: 'Terzina 60°', numbers: [n, e1, e2], color: '#d98e2b' });
        const q1 = rot(n, 22), q2 = rot(n, 45), q3 = rot(n, 67);
        if (S.has(q1) && S.has(q2) && S.has(q3) && n < q1 && q1 < q2)
          figs.push({ type: 'Quadrato', numbers: [n, q1, q2, q3], color: '#2980b9' });
      });
      return figs;
    }

    function updateFigureList() {
      document.getElementById('figureList').innerHTML = detectGeometricFigures().map(f => `<div class="figure-item"><span class="type">${f.type}</span><span>${f.numbers.join(' - ')}</span></div>`).join('');
    }

    function detectGeometricFigures() {
      const figs = [], S = new Set([...highlighted]);
      [...highlighted].forEach(n => {
        const o = rot(n, 45);
        if (S.has(o) && n < o) figs.push({ type: 'Opposti 180°', numbers: [n, o], color: '#c0392b' });
        const t1 = rot(n, 30), t2 = rot(n, 60);
        if (S.has(t1) && S.has(t2) && n < t1 && t1 < t2) figs.push({ type: 'Triangolo 120°', numbers: [n, t1, t2], color: '#2e8b57' });
        const e1 = rot(n, 15), e2 = rot(n, 30);
        if (S.has(e1) && S.has(e2) && n < e1 && e1 < e2) figs.push({ type: 'Terzina 60°', numbers: [n, e1, e2], color: '#d98e2b' });
        const q1 = rot(n, 22), q2 = rot(n, 45), q3 = rot(n, 67);
        if (S.has(q1) && S.has(q2) && S.has(q3) && n < q1 && q1 < q2)
          figs.push({ type: 'Quadrato 90°', numbers: [n, q1, q2, q3], color: '#2980b9' });
      });
      return figs;
    }

    function findNumberPosition(num) {
      for (let idx = 0; idx < RINGS.length; idx++) {
        const ring = RINGS[idx];
        const pos = ring.numbers.indexOf(num);
        if (pos !== -1) {
          const rMid = (ring.rIn + ring.rOut) / 2;
          const ang = (pos + 0.5) * SECTOR_ANGLE + offset[idx];
          const [x, y] = polar(CX, CY, rMid, ang);
          return { x, y };
        }
      }
      return null;
    }

    function drawOppositeLines() {
      document.querySelectorAll('.opposite-line').forEach(l => l.remove());
      const nums = [...highlighted];
      nums.forEach(n => {
        const o = rot(n, 45);
        if (nums.includes(o) && n < o) {
          const p1 = findNumberPosition(n), p2 = findNumberPosition(o);
          if (p1 && p2) svg.insertBefore(el('line', { x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y, class: 'opposite-line' }), svg.querySelector('#lines-0'));
        }
      });
    }

    function angleOfPoint(x, y) { return (Math.atan2(y - CY, x - CX) * 180 / Math.PI + 90 + 360) % 360; }

    function midPoint(pts) {
      const mx = pts.reduce((s, p) => s + p.x, 0) / pts.length;
      const my = pts.reduce((s, p) => s + p.y, 0) / pts.length;
      return { x: mx, y: my };
    }

    function arcBetween(p1, p2, r) {
      const a1 = angleOfPoint(p1.x, p1.y);
      const a2 = angleOfPoint(p2.x, p2.y);
      let start = a1, end = a2;
      let diff = (end - start + 360) % 360;
      let large = diff > 180 ? 1 : 0;
      let sweep = 1;
      if (diff > 180) {
        [start, end] = [end, start];
        diff = (end - start + 360) % 360;
        large = diff > 180 ? 1 : 0;
      }
      const s = polar(CX, CY, r, start);
      const e = polar(CX, CY, r, end);
      return `M ${s[0]} ${s[1]} A ${r} ${r} 0 ${large} ${sweep} ${e[0]} ${e[1]}`;
    }

    function polygonPath(pts) { return pts.map((p, i) => `${i ? 'L' : 'M'} ${p.x} ${p.y}`).join(' ') + ' Z'; }

    function addLabel(cx, cy, text, color) {
      const pad = 5;
      const w = text.length * 6.5 + pad * 2;
      const bg = el('rect', { x: cx - w / 2, y: cy - 9, width: w, height: 18, rx: 9, fill: 'rgba(0,0,0,.6)', stroke: color, 'stroke-width': 1 });
      const lab = el('text', { x: cx, y: cy + 0.5, class: 'fig-label', fill: '#fff' });
      lab.textContent = text;
      overlayG.appendChild(bg);
      overlayG.appendChild(lab);
    }

    // ============================================================
    // v2.7: VERTICI INTERATTIVI
    // ============================================================
    function revealNumber(num, on) {
      const t = svg.querySelector(`text.num[data-number="${num}"]`);
      if (!t) return;
      if (on) {
        t.dataset.origfs = t.getAttribute('font-size') || t.dataset.origfs || '';
        t.setAttribute('font-size', (parseFloat(t.getAttribute('font-size')) || 16) + 7);
        t.setAttribute('font-weight', '800');
        t.style.paintOrder = 'stroke';
        t.style.stroke = 'rgba(255,255,255,.85)';
        t.style.strokeWidth = '3px';
        overlayG.querySelectorAll(`[data-num="${num}"]`).forEach(c => { c.style.opacity = '0.25'; });
      } else {
        if (t.dataset.origfs) t.setAttribute('font-size', t.dataset.origfs);
        t.removeAttribute('font-weight');
        t.style.stroke = '';
        t.style.strokeWidth = '';
        t.style.paintOrder = '';
        overlayG.querySelectorAll(`[data-num="${num}"]`).forEach(c => { c.style.opacity = ''; });
      }
    }

    function drawVertex(p, color, r = 6, num = null) {
      const v = el('circle', { cx: p.x, cy: p.y, r: r, class: 'fig-vertex', fill: color, stroke: '#fff' });
      const ring = el('circle', { cx: p.x, cy: p.y, r: r + 4, class: 'fig-vertex-ring', stroke: color });
      if (num != null) {
        [v, ring].forEach(c => {
          c.setAttribute('data-num', num);
          c.style.pointerEvents = 'all';
          c.style.cursor = 'pointer';
          c.addEventListener('pointerenter', () => revealNumber(num, true));
          c.addEventListener('pointerleave', () => revealNumber(num, false));
          c.addEventListener('pointerdown', (ev) => { revealNumber(num, true); setTimeout(() => revealNumber(num, false), 1200); ev.stopPropagation(); });
        });
      }
      overlayG.appendChild(v);
      overlayG.appendChild(ring);
    }

    function drawRays(pts, color) {
      pts.forEach(p => { overlayG.appendChild(el('line', { x1: CX, y1: CY, x2: p.x, y2: p.y, class: 'fig-ray', stroke: color })); });
    }

    function drawFigOpposti(pts, color, label) {
      const [p1, p2] = pts;
      overlayG.appendChild(el('line', { x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y, class: 'fig-diameter fig-pulse', stroke: color }));
      const rOut = 295;
      overlayG.appendChild(el('path', { d: arcBetween(p1, p2, rOut), class: 'fig-arc fig-pulse', stroke: color }));
      overlayG.appendChild(el('path', { d: arcBetween(p2, p1, rOut), class: 'fig-arc-inner', stroke: color }));
      const cr = 8;
      overlayG.appendChild(el('circle', { cx: CX, cy: CY, r: cr, class: 'fig-center', fill: 'none', stroke: color, 'stroke-width': 2 }));
      overlayG.appendChild(el('line', { x1: CX - cr, y1: CY, x2: CX + cr, y2: CY, class: 'fig-crosshair', stroke: color }));
      overlayG.appendChild(el('line', { x1: CX, y1: CY - cr, x2: CX, y2: CY + cr, class: 'fig-crosshair', stroke: color }));
      drawVertex(p1, color, 6, p1.num); drawVertex(p2, color, 6, p2.num);
      const mid = midPoint(pts);
      const dx = mid.x - CX, dy = mid.y - CY;
      const dist = Math.hypot(dx, dy) || 1;
      const lx = CX + dx / dist * 60, ly = CY + dy / dist * 60;
      addLabel(lx, ly, label, color);
      const ang = el('text', { x: CX, y: CY + 28, class: 'fig-angle-label', fill: color });
      ang.textContent = '180°';
      overlayG.appendChild(ang);
    }

    function drawFigTriangolo(pts, color, label) {
      overlayG.appendChild(el('path', { d: polygonPath(pts), class: 'fig-shape fig-pulse', fill: color, stroke: color }));
      drawRays(pts, color);
      const rOut = 295;
      overlayG.appendChild(el('path', { d: arcBetween(pts[0], pts[1], rOut), class: 'fig-arc', stroke: color }));
      overlayG.appendChild(el('path', { d: arcBetween(pts[1], pts[2], rOut), class: 'fig-arc', stroke: color }));
      overlayG.appendChild(el('path', { d: arcBetween(pts[2], pts[0], rOut), class: 'fig-arc', stroke: color }));
      pts.forEach(p => drawVertex(p, color, 6, p.num));
      const c = midPoint(pts);
      overlayG.appendChild(el('circle', { cx: c.x, cy: c.y, r: 4, class: 'fig-center', fill: color, stroke: '#fff', 'stroke-width': 1.5 }));
      addLabel(c.x, c.y - 18, label, color);
    }

    function drawFigTerzina(pts, color, label) {
      const rOut = 295;
      overlayG.appendChild(el('path', { d: arcBetween(pts[0], pts[2], rOut), class: 'fig-arc fig-pulse', stroke: color }));
      overlayG.appendChild(el('path', { d: polygonPath(pts), class: 'fig-shape', fill: color, stroke: color }));
      drawRays(pts, color);
      const rIn = 70;
      overlayG.appendChild(el('path', { d: arcBetween(pts[0], pts[2], rIn), class: 'fig-arc-inner', stroke: color }));
      pts.forEach(p => drawVertex(p, color, 5, p.num));
      const c = midPoint(pts);
      overlayG.appendChild(el('circle', { cx: c.x, cy: c.y, r: 3.5, class: 'fig-center', fill: color, stroke: '#fff', 'stroke-width': 1.5 }));
      addLabel(c.x, c.y - 16, label, color);
    }

    function drawFigQuadrato(pts, color, label) {
      overlayG.appendChild(el('path', { d: polygonPath(pts), class: 'fig-shape fig-pulse', fill: color, stroke: color }));
      drawRays(pts, color);
      overlayG.appendChild(el('line', { x1: pts[0].x, y1: pts[0].y, x2: pts[2].x, y2: pts[2].y, class: 'fig-ray', stroke: color }));
      overlayG.appendChild(el('line', { x1: pts[1].x, y1: pts[1].y, x2: pts[3].x, y2: pts[3].y, class: 'fig-ray', stroke: color }));
      const rOut = 295;
      for (let i = 0; i < 4; i++) {
        overlayG.appendChild(el('path', { d: arcBetween(pts[i], pts[(i + 1) % 4], rOut), class: 'fig-arc', stroke: color }));
      }
      pts.forEach(p => drawVertex(p, color, 6, p.num));
      const c = midPoint(pts);
      overlayG.appendChild(el('circle', { cx: c.x, cy: c.y, r: 4, class: 'fig-center', fill: color, stroke: '#fff', 'stroke-width': 1.5 }));
      addLabel(c.x, c.y - 18, label, color);
    }

    function drawFigDefault(pts, color, label) {
      overlayG.appendChild(el('path', { d: polygonPath(pts), class: 'fig-shape', fill: color, stroke: color }));
      drawRays(pts, color);
      pts.forEach(p => drawVertex(p, color, 5, p.num));
      const c = midPoint(pts);
      addLabel(c.x, c.y - 16, label, color);
    }

    // ============================================================
    // FIGURA DINAMICA (v2.5)
    // ============================================================
    function drawFigDinamica(pts, color, label) {
      overlayG.appendChild(el('path', { d: polygonPath(pts), class: 'fig-shape fig-pulse', fill: color, stroke: color, 'stroke-dasharray': '6 4' }));
      drawRays(pts, color);
      pts.forEach(p => drawVertex(p, color, 5, p.num));
      const c = midPoint(pts);
      overlayG.appendChild(el('circle', { cx: c.x, cy: c.y, r: 4, class: 'fig-center', fill: color, stroke: '#fff', 'stroke-width': 1.5 }));
      addLabel(c.x, c.y - 16, label, color);
    }

    // ============================================================
    // v2.6: FIGURE VISIBILI = classiche + dinamiche filtrate
    // ============================================================
    function getVisibleFigures() {
      const vis = [...ENGINE.figures];
      if (ENGINE.activeDist === 'all') {
        vis.push(...ENGINE.figDin);
      } else if (typeof ENGINE.activeDist === 'number') {
        ENGINE.figDin.forEach(f => { if (f.dist === ENGINE.activeDist) vis.push(f); });
      }
      return vis;
    }

    function drawFigureOverlay() {
      if (!overlayG) return;
      overlayG.innerHTML = '';
      getVisibleFigures().forEach(f => {
        const pts = f.numbers.map(n => {
          const pos = findNumberPosition(n);
          return pos ? { x: pos.x, y: pos.y, num: n } : null;
        });
        if (pts.some(p => !p)) return;
        if (pts.length < 2) return;
        switch (f.type) {
          case 'Opposti': drawFigOpposti(pts, f.color, 'Opposti 180°'); break;
          case 'Triangolo': drawFigTriangolo(pts, f.color, 'Triangolo 120°'); break;
          case 'Terzina 60°': drawFigTerzina(pts, f.color, 'Terzina 60°'); break;
          case 'Quadrato': drawFigQuadrato(pts, f.color, 'Quadrato 90°'); break;
          case 'Dinamica': drawFigDinamica(pts, f.color, `Figura Δ${f.dist} ×${f.count}`); break;
          default: drawFigDefault(pts, f.color, f.type);
        }
      });
    }

    function getBase() { return drawHighlighted.size >= 2 ? [...drawHighlighted] : [...highlighted]; }

    function computeCandidates(base) {
      const scores = new Map();
      const add = (n, peso, motivo) => {
        n = fuori90(n);
        if (base.includes(n)) return;
        if (!scores.has(n)) scores.set(n, { n, score: 0, motivi: [] });
        const c = scores.get(n);
        c.score += peso;
        if (!c.motivi.includes(motivo)) c.motivi.push(motivo);
      };
      base.forEach(n => {
        add(rot(n, 45), 3.0, `opposto di ${n}`);
        add(rot(n, 30), 2.0, `trigono di ${n}`);
        add(rot(n, 60), 2.0, `trigono di ${n}`);
        add(rot(n, 15), 1.5, `sesto di ${n}`);
        const ds = String(n).split('').reduce((s, d) => s + +d, 0);
        const ridotto = ds > 9 ? ds - 9 : ds;
        add(ridotto, 0.8, `somma cifre di ${n}`);
      });
      for (let i = 0; i < base.length; i++) for (let j = i + 1; j < base.length; j++) {
        const a = base[i], b = base[j];
        const dc = Math.min(Math.abs(a - b), 90 - Math.abs(a - b));
        add(dc, 1.2, `distanza di ${a}-${b}`);
        const s = a + b;
        add(s > 90 ? s - 90 : s, 1.2, `somma di ${a}+${b}`);
        if ((a + b) % 2 === 0) {
          const m = (a + b) / 2;
          add(m, 1.5, `equidistante di ${a}-${b}`);
          add(rot(m, 45), 1.5, `opposto equidistante`);
        }
      }
      return [...scores.values()].sort((x, y) => y.score - x.score || x.n - y.n);
    }

    // ============================================================
    // MOTORE PREVISIVO v2.6/v2.7
    // ============================================================
    function runEngine() {
      const base = getBase();
      if (base.length < 2) { showNotification('️ Carica prima almeno 2 numeri'); return; }
      ENGINE.base = base;
      let cands = computeCandidates(base);
      const firma = firmaGeometrica(base);
      ENGINE.firma = firma;
      const dominanti = (firma.filter(f => f.count >= 2).length ? firma.filter(f => f.count >= 2) : firma.slice(0, 2));
      const figDin = [];
      firma.forEach(sd => {
        const vertici = new Set();
        sd.pairs.forEach(pair => { vertici.add(pair[0]); vertici.add(pair[1]); });
        const vertArr = [...vertici].sort((a, b) => a - b);
        if (vertArr.length >= 2) {
          figDin.push({ type: 'Dinamica', numbers: vertArr, color: '#9b59b6', dist: sd.d, count: sd.count });
        }
        if (dominanti.includes(sd)) {
          vertArr.forEach(v => {
            [sd.d, 90 - sd.d].forEach(step => {
              const n = fuori90(v + step);
              const existing = cands.find(c => c.n === n);
              if (existing) {
                existing.score += 1.5 * sd.count;
                if (!existing.motivi.includes(`sviluppo Δ${sd.d}`)) existing.motivi.push(`sviluppo Δ${sd.d}`);
              } else if (!base.includes(n)) {
                cands.push({ n, score: 1.5 * sd.count, motivi: [`sviluppo Δ${sd.d} da ${v}`] });
              }
            });
          });
        }
      });
      cands.sort((x, y) => y.score - x.score || x.n - y.n);
      ENGINE.candidates = cands;
      ENGINE.figures = detectBaseFigures(base);
      ENGINE.figDin = figDin;
      ENGINE.activeDist = dominanti.length ? dominanti[0].d : null;
      renderEnginePanel();
      drawFigureOverlay();
      if (heatOn) applyHeat();
      showNotification(`⚡ Firma: Δ${firma.map(f => f.d).join(', Δ')} · selezionata Δ${ENGINE.activeDist}`);
      renderNumeriPromettenti();
    }

    function renderEnginePanel() {
      const box = document.getElementById('engineResults');
      const c = ENGINE.candidates;
      if (!c.length) { box.innerHTML = ''; return; }
      const pick = k => c.slice(0, k).map(x => x.n).join(' – ');
      const counts = {};
      getVisibleFigures().forEach(f => counts[f.type] = (counts[f.type] || 0) + 1);
      const badges = Object.entries(counts).map(([t, k]) => {
        const col = (ENGINE.figures.find(f => f.type === t) || ENGINE.figDin.find(f => f.type === t) || { color: '#9b59b6' }).color;
        return `<span class="fig-badge" style="border-color:${col};color:${col}">${k}× ${t}</span>`;
      }).join('');
      let firmaHtml = '';
      if (ENGINE.firma && ENGINE.firma.length) {
        firmaHtml = `<div style="margin-top:8px;font-size:11px;color:#c9b98f;">Firma geometrica — <b>clicca una Δ per vedere SOLO quella figura</b> (riclic = nascondi):</div>`;
        firmaHtml += `<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:4px;">`;
        ENGINE.firma.slice(0, 8).forEach(f => {
          const active = ENGINE.activeDist === f.d;
          firmaHtml += `<button class="fig-badge firma-badge" data-dist="${f.d}" style="border-color:${active ? '#e0b04a' : (f.count >= 2 ? '#9b59b6' : '#5a4630')};color:${active ? '#e0b04a' : (f.count >= 2 ? '#9b59b6' : '#b3a37e')};background:${active ? 'rgba(224,176,74,.18)' : 'transparent'};font-weight:${active ? '700' : '400'}">Δ${f.d} ×${f.count}</button>`;
        });
        firmaHtml += `<button class="fig-badge firma-badge" id="firmaAll" style="border-color:${ENGINE.activeDist === 'all' ? '#e0b04a' : '#5a4630'};color:${ENGINE.activeDist === 'all' ? '#e0b04a' : '#b3a37e'}">👁 Tutte</button>`;
        firmaHtml += `</div>`;
        if (typeof ENGINE.activeDist === 'number') {
          const sel = ENGINE.figDin.filter(f => f.dist === ENGINE.activeDist);
          if (sel.length) {
            const vert = [...new Set(sel.flatMap(f => f.numbers))].sort((a, b) => a - b);
            const dev = [...new Set(vert.flatMap(v => [fuori90(v + ENGINE.activeDist), fuori90(v + 90 - ENGINE.activeDist)]))].filter(n => !vert.includes(n)).sort((a, b) => a - b);
            firmaHtml += `<div style="margin-top:6px;font-size:11px;color:#c9b98f;">Vertici Δ${ENGINE.activeDist}: <b style="color:#9b59b6">${vert.join(', ')}</b></div>`;
            firmaHtml += `<div style="margin-top:3px;font-size:11px;color:#c9b98f;">Sviluppi (Δ e 90−Δ): <b style="color:#e0b04a">${dev.join(', ')}</b></div>`;
            firmaHtml += `<div class="btn-row" style="margin-top:6px;"><button class="step" id="firmaHighlight">🎯 Evidenzia vertici + sviluppi</button></div>`;
          }
        }
      }
      box.innerHTML = firmaHtml + `
        <div style="margin-top:10px;">${badges || '<span style="font-size:12px;color:#8a7a5c">Nessuna figura</span>'}</div>
        <div class="pred-grid">
          <div class="pred-box"><div class="lbl">Amata</div><div class="val">${c[0]?.n ?? '—'}</div></div>
          <div class="pred-box"><div class="lbl">Ambo</div><div class="val" style="font-size:16px;">${pick(2) || '—'}</div></div>
          <div class="pred-box"><div class="lbl">Terno</div><div class="val" style="font-size:15px;">${pick(3) || '—'}</div></div>
          <div class="pred-box"><div class="lbl">Cinquina</div><div class="val" style="font-size:13px;">${pick(5) || '—'}</div></div>
        </div>
        <div class="cand-list">${c.slice(0, 8).map(x => `
          <div class="cand-item" data-n="${x.n}">
            <span class="cn">${x.n}</span>
            <span class="cm">${x.motivi.slice(0, 2).join(' • ')}</span>
            <span class="cs">${x.score.toFixed(1)}</span>
          </div>`).join('')}
        </div>`;
      box.querySelectorAll('.firma-badge[data-dist]').forEach(b => {
        b.addEventListener('click', () => {
          const d = parseInt(b.dataset.dist);
          ENGINE.activeDist = (ENGINE.activeDist === d) ? null : d;
          renderEnginePanel();
          drawFigureOverlay();
        });
      });
      const allBtn = box.querySelector('#firmaAll');
      if (allBtn) allBtn.addEventListener('click', () => {
        ENGINE.activeDist = (ENGINE.activeDist === 'all') ? null : 'all';
        renderEnginePanel();
        drawFigureOverlay();
      });
      const hlBtn = box.querySelector('#firmaHighlight');
      if (hlBtn) hlBtn.addEventListener('click', () => {
        const sel = ENGINE.figDin.filter(f => f.dist === ENGINE.activeDist);
        const vert = [...new Set(sel.flatMap(f => f.numbers))];
        const dev = [...new Set(vert.flatMap(v => [fuori90(v + ENGINE.activeDist), fuori90(v + 90 - ENGINE.activeDist)]))].filter(n => !vert.includes(n));
        [...vert, ...dev].forEach(n => highlighted.add(n));
        renderChips(); renderAll(); aggiornaCadenzeDecine();
        showNotification(`🎯 ${vert.length + dev.length} numeri evidenziati`);
      });
      box.querySelectorAll('.cand-item').forEach(it => {
        it.addEventListener('click', () => { highlighted.add(parseInt(it.dataset.n)); renderChips(); renderAll(); aggiornaCadenzeDecine(); });
      });
    }

    function toggleHeat() {
      heatOn = !heatOn;
      if (heatOn && ENGINE.candidates.length === 0) runEngine();
      heatMap = new Map(ENGINE.candidates.slice(0, 30).map((c, i) => [c.n, 1 - i / 30]));
      applyHeat();
      showNotification(heatOn ? '🔥 Mappa calore attiva' : 'Mappa calore spenta');
    }

    function applyHeat() {
      ringGroups.forEach(rg => {
        rg.texts.forEach((t, i) => {
          const n = rg.ring.numbers[i];
          if (heatOn && heatMap.has(n)) {
            const k = heatMap.get(n);
            t.style.fill = `rgba(224,${Math.round(176 - 60 * k)},40,${0.35 + 0.65 * k})`;
            t.style.fontSize = (rg.ring.fs + 2 * k) + 'px';
          } else { t.style.fill = ''; t.style.fontSize = ''; }
        });
      });
    }

    function startTour() {
      const vis = getVisibleFigures();
      if (!vis.length) { showNotification('⚠️ Prima premi ⚡'); return; }
      stopTour();
      let i = 0;
      const out = document.getElementById('alignmentResult');
      const step = () => {
        const f = vis[i];
        out.innerHTML = `<span><strong>${i + 1}/${vis.length}:</strong> ${f.type === 'Dinamica' ? `Figura Δ${f.dist}` : f.type} → ${f.numbers.join(' – ')}</span>`;
        tourTimer = setTimeout(() => {
          i++;
          if (i >= vis.length) { stopTour(); showNotification('🎬 Tour completato'); }
          else step();
        }, 2600);
      };
      step();
    }

    function stopTour() { if (tourTimer) { clearTimeout(tourTimer); tourTimer = null; } }

    function clearEngine() {
      stopTour();
      ENGINE.figures = []; ENGINE.candidates = []; ENGINE.base = []; ENGINE.firma = []; ENGINE.figDin = []; ENGINE.activeDist = null;
      heatOn = false; heatMap.clear(); applyHeat(); drawFigureOverlay();
      document.getElementById('engineResults').innerHTML = '';
      showNotification(' Motore ripulito');
    }

    // ============================================================
    // v2.8/v2.9: LOGICA 38 — COLONNE DI CADENZA
    // Rc = ritardo corrente · Rx = MASSIMO ritardo storico
    // ============================================================
    let L38 = { ruota: null, num: null, cadenza: null, colonne: [], excl: [2, 2, 2], ambi: [], esclusi: [], prevHl: [], shrinkFinal: [] };

    function radice(n) { let s = n; while (s > 9) s = String(s).split('').reduce((a, d) => a + +d, 0); return s; }

    function novinaCadenza(c) { c = ((c % 10) + 10) % 10; const out = []; for (let n = 1; n <= 90; n++) if (n % 10 === c) out.push(n); return out; }

    function colonnaIndexOf(num) { for (let r = 0; r < 3; r++) { const i = RINGS[r].numbers.indexOf(num); if (i !== -1) return { ring: r, idx: i }; } return null; }

    function colonneCadenza(c) {
      const m = new Map();
      novinaCadenza(c).forEach(n => { const p = colonnaIndexOf(n); if (!p) return; if (!m.has(p.idx)) m.set(p.idx, {}); m.get(p.idx)[p.ring] = n; });
      return [...m.entries()].sort((a, b) => a[0] - b[0]).map(([idx, o]) => ({ idx, r0: o[0], r1: o[1], r2: o[2] }));
    }

    function subRuota(ruota) { return archivio ? archivio.righe.filter(r => r.ruota === ruota).sort((a, b) => parseData(a.data) - parseData(b.data)) : []; }

    function subRuotaL38() {
      let sub = subRuota(L38.ruota);
      const ds = document.getElementById('l38Date').value;
      if (ds) { const d = parseData(ds); if (d) sub = sub.filter(r => parseData(r.data) <= d); }
      return sub;
    }

    function setupL38DateRange() {
      const inp = document.getElementById('l38Date');
      if (archivio && archivio.righe.length) {
        const dates = archivio.righe.map(r => parseData(r.data)).filter(Boolean).sort((a, b) => a - b);
        inp.min = toISOData(dates[0]);
        inp.max = toISOData(dates[dates.length - 1]);
        if (!inp.value) inp.value = toISOData(dates[dates.length - 1]);
      } else {
        if (!inp.value) inp.value = toISOData(new Date());
      }
    }

    // v2.9.1: Rc/Rx CALIBRATI - NO tratto iniziale in Rx
    function delayAmbo(nums, sub){
      for(let i=sub.length-1; i>=0; i--){
        const d=sub[i]; let c=0;
        [d.n1,d.n2,d.n3,d.n4,d.n5].forEach(n=>{ if(nums.includes(n)) c++; });
        if(c>=2) return sub.length-1-i;
      }
      return sub.length;
    }

    function maxGapAmbo(nums, sub){
      let lastHit=-1, maxGap=0, hits=0;
      for(let i=0;i<sub.length;i++){
        const d=sub[i]; let c=0;
        [d.n1,d.n2,d.n3,d.n4,d.n5].forEach(n=>{ if(nums.includes(n)) c++; });
        if(c>=2){
        if(lastHit>=0){ const g=i-lastHit-1; if(g>maxGap) maxGap=g; }  // -1: il concorso i è quello di uscita
        lastHit=i; hits++;
        }
      }
      if(hits<2) return 0; // Non abbiamo abbastanza dati per calcolare max gap
      return maxGap;
    }

    function meanGapAmbo(nums, sub){
      let hits=0;
      for(let i=0;i<sub.length;i++){
        const d=sub[i]; let c=0;
        [d.n1,d.n2,d.n3,d.n4,d.n5].forEach(n=>{ if(nums.includes(n)) c++; });
        if(c>=2) hits++;
      }
      if(!hits) return 400.5/Math.max(1, nums.length*(nums.length-1)/2);
      return sub.length/hits;
    }

    function delayTerna(t, sub){ return delayAmbo(t, sub); }

    function runLogica38() {
      const ruota = document.getElementById('l38Ruota').value;
      const num = parseInt(document.getElementById('l38Num').value);
      if (!num || num < 1 || num > 90) { showNotification('⚠️ Inserisci il primo estratto (1-90)'); return; }
      const mode = document.getElementById('l38Mode').value;
      let cad;
      if (mode === 'radice') cad = radice(num) % 10;
      else if (mode === 'compl') cad = (90 - num) % 10;
      else cad = parseInt(document.getElementById('l38Cad').value) || 0;
      L38.ruota = ruota; L38.num = num; L38.cadenza = cad;
      L38.colonne = colonneCadenza(cad);
      L38.excl = [2, 2, 2];
      renderL38Cols();
      renderL38Tracker();
      showNotification(`🧭 ${CODICE_TO_NOME[ruota]} n.${num} → cadenza ${cad} · 3 colonne sul cerchio`);
    }

    function renderL38Cols() {
      const box = document.getElementById('l38Cols');
      if (!L38.colonne.length) { box.innerHTML = ''; return; }
      L38.prevHl.forEach(n => highlighted.delete(n));
      L38.ambi = []; L38.esclusi = [];
      let h = `<div style="margin-top:8px;font-size:11px;color:#c9b98f;">Cadenza ${L38.cadenza} · 3 colonne (ambo = interno+medio, escluso = esterno,  scambia):</div>`;
      L38.colonne.forEach((col, ci) => {
        const ex = L38.excl[ci];
        const rings = [0, 1, 2].filter(r => r !== ex);
        const ambo = [col['r' + rings[0]], col['r' + rings[1]]];
        const escl = col['r' + ex];
        L38.ambi.push(ambo); L38.esclusi.push(escl);
        h += `<div class="l38-col">`;
        [0, 1, 2].forEach(r => { const n = col['r' + r]; h += `<div class="l38-cell ${r === ex ? 'escl' : 'ambo'}">${n}</div>`; });
        h += `<button class="l38-swap" data-col="${ci}">⇄</button></div>`;
      });
      h += `<div style="margin-top:4px;font-size:11px;color:#c9b98f;">Ambi secchi: <b style="color:var(--gold-fill)">${L38.ambi.map(a => a.join('-')).join(' · ')}</b> · Esclusi: <span style="color:#e0a0a0">${L38.esclusi.join(', ')}</span></div>`;
      box.innerHTML = h;
      L38.prevHl = L38.ambi.flat();
      L38.prevHl.forEach(n => highlighted.add(n));
      renderChips(); renderAll(); aggiornaCadenzeDecine();
      box.querySelectorAll('.l38-swap').forEach(b => b.addEventListener('click', () => {
        const ci = +b.dataset.col;
        L38.excl[ci] = (L38.excl[ci] + 1) % 3;
        renderL38Cols();
      }));
    }

    function runShrink38(){
      if(L38.cadenza==null){ showNotification('⚠️ Prima imposta il cerchio (🧭)'); return; }
      if(!archivio || !archivio.righe.length){ showNotification('⚠️ Carica l\'archivio'); return; }
      const sub = subRuotaL38();
      if(sub.length < 50){ showNotification('⚠️ Servono almeno 50 estrazioni fino alla data scelta'); return; }
      let combo = novinaCadenza(L38.cadenza);
      const ds = document.getElementById('l38Date').value;
      let h = `<div style="margin-top:8px;font-size:11px;color:#c9b98f;">📉 Shrink lunghetta cad.${L38.cadenza} (${L38.ruota}) — classi 8→2 · al ${ds||'ultima data'}:</div>`;
      for(let cls=8; cls>=2; cls--){
        const opts = [];
        for(let i=0;i<combo.length;i++){
          const rest = combo.filter((_,j)=>j!==i);
          const rc = delayAmbo(rest, sub);
          const rx = maxGapAmbo(rest, sub);
          opts.push({excl:combo[i], rest, rc, rx});
        }
        opts.sort((a,b)=>((b.rc>=b.rx)?1:0)-((a.rc>=a.rx)?1:0)||b.rc-a.rc);
        h += `<div class="shrink-line"><b>--- SVILUPPO CLASSE ${cls} ---</b></div>`;
        opts.slice(0,2).forEach((o,pi)=>{
          h += `<div class="shrink-line">Pos.${pi+1} [Rc ${o.rc} / Rx ${o.rx}] ${o.rc>=o.rx?'<span class="ok">≥</span>':'<span class="no">&lt;</span>'} → Escluso n° <b>${o.excl}</b> → ${o.rest.join('.')}</div>`;
        });
        combo = opts[0].rest;
      }
      L38.shrinkFinal = combo;
      h += `<div style="margin-top:4px;font-size:11px;color:#c9b98f;">Esito shrink: <b style="color:var(--gold-fill)">${combo.join(' - ')}</b></div>`;
      document.getElementById('l38Results').innerHTML = h;
      showNotification(`📉 Shrink completato: ${combo.join('-')}`);
    }

    function partitions280(nums) {
      const res = [];
      for (let a = 1; a < 9; a++) for (let b = a + 1; b < 9; b++) {
        const t1 = [nums[0], nums[a], nums[b]];
        const rem = nums.filter((_, i) => i !== 0 && i !== a && i !== b);
        for (let c = 1; c < rem.length; c++) for (let d = c + 1; d < rem.length; d++) {
          const t2 = [rem[0], rem[c], rem[d]];
          const t3 = rem.filter((_, i) => i !== 0 && i !== c && i !== d);
          res.push([t1, t2, t3]);
        }
      }
      return res;
    }

    function runTerzine280() {
      if (L38.cadenza == null) { showNotification('⚠️ Prima imposta il cerchio (🧭)'); return; }
      if (!archivio || !archivio.righe.length) { showNotification('️ Carica l\'archivio'); return; }
      const sub = subRuotaL38();
      if (sub.length < 50) { showNotification('⚠️ Servono almeno 50 estrazioni fino alla data scelta'); return; }
      const parts = partitions280(novinaCadenza(L38.cadenza));
      const rows = parts.map(g=>{
        let rcSum = 0, rxSum = 0;
        const seq = [];
        g.forEach(t=>{
        rcSum += delayTerna(t, sub);
        rxSum += Math.round(meanGapAmbo(t, sub));   // Rx = attesa empirica (somma delle attese, come da Guida)
        const s=[...t].sort((x,y)=>x-y);
        seq.push(distanzaCiclo(s[0],s[1]), distanzaCiclo(s[1],s[2]));
        });
        const sim = seq.every((v,i)=>v===seq[seq.length-1-i]);
        return {g, rc:rcSum, rx:Math.round(rxSum), seq, sim};
      });
      const filt = rows.filter(r => r.rc >= r.rx).sort((a, b) => b.rc - a.rc);
      const maxRc = filt.length ? filt[0].rc : 0;
      let h = `<div style="margin-top:8px;font-size:11px;color:#c9b98f;">🧩 Gruppi di 3 terzine cad.${L38.cadenza} (${L38.ruota}) · sviluppate ${rows.length} · filtrate Rc≥Rx ${filt.length} · ritardo max ${maxRc}</div>`;
      filt.slice(0, 16).forEach(r => {
        h += `<div class="shrink-line">${r.g.map(t => [...t].sort((a, b) => a - b).join('.')).join('  ')} · Rc ${r.rc} · Rx ${r.rx}${r.sim ? ' · <span class="ok">Simmetriche</span>' : ''} · ${r.seq.join('.')}</div>`;
      });
      document.getElementById('l38Results').innerHTML = h;
      showNotification(`🧩 280 terzine: ${filt.length} filtrate Rc≥Rx · max Rc ${maxRc}`);
    }

    function saveL38() {
      if (!L38.ambi.length) { showNotification('⚠️ Prima imposta il cerchio (🧭)'); return; }
      const list = JSON.parse(localStorage.getItem('l38preds') || '[]');
      list.push({ ruota: L38.ruota, num: L38.num, cad: L38.cadenza, ambi: L38.ambi, esclusi: L38.esclusi, shrink: L38.shrinkFinal || [], salvataIl: new Date().toISOString() });
      localStorage.setItem('l38preds', JSON.stringify(list));
      renderL38Tracker();
      showNotification(' Previsione salvata (durata 9 concorsi)');
    }
    
    // ============================================================
    // v2.10.1: RICERCA AUTOMATICA PER DOPPIE DISTANZE — LOGICA COMPLETA CORRETTA
    // Le uniche terzine con una distanza ripetuta 2 volte (doppia distanza Δ)
    // sono le terzine simmetriche/in progressione {x, x+Δ, x+2Δ} (mod 90).
    // Rc = ritardo corrente ambo nella terzina
    // Rx att. = attesa empirica (media gap) · Rx max = massimo ritardo storico CHIUSO (corretto -1)
    // Filtro gruppo: Rc ≥ Rx(attesa). Monitor multi-ruota + inserimento nel Cerchio.
    // ============================================================
    let DIST_FOUND = [];
    let DIST_RUOTE_MONITOR = [];

    function calcolaDistanzeCiclometriche(numeri) {
      const distanze = [];
      for (let i = 0; i < numeri.length; i++) {
        for (let j = i + 1; j < numeri.length; j++) {
          distanze.push({ n1: numeri[i], n2: numeri[j], d: distanzaCiclo(numeri[i], numeri[j]) });
        }
      }
      return distanze;
    }

    function trovaDoppieDistanze(distanze) {
      const conteggio = {};
      distanze.forEach(d => { conteggio[d.d] = (conteggio[d.d] || 0) + 1; });
      return Object.entries(conteggio)
        .filter(([d, count]) => count >= 2)
        .map(([d, count]) => ({ d: parseInt(d), count }))
        .sort((a, b) => b.count - a.count || a.d - b.d);
    }

    // Statistiche ambo in UN solo passaggio (gap chiusi corretti: g = i - lastHit - 1)
    function statisticheAmbo(nums, sub) {
      const S = new Set(nums);
      let lastHit = -1, maxGap = 0, hits = 0;
      for (let i = 0; i < sub.length; i++) {
        const d = sub[i];
        let c = 0;
        if (S.has(d.n1)) c++;
        if (S.has(d.n2)) c++;
        if (S.has(d.n3)) c++;
        if (S.has(d.n4)) c++;
        if (S.has(d.n5)) c++;
        if (c >= 2) {
          if (lastHit >= 0) { const g = i - lastHit - 1; if (g > maxGap) maxGap = g; }
          lastHit = i; hits++;
        }
      }
      const rc = lastHit >= 0 ? sub.length - 1 - lastHit : sub.length;
      const rxMax = hits >= 2 ? maxGap : 0;
      const rxAtt = hits > 0 ? sub.length / hits : 400.5 / Math.max(1, nums.length * (nums.length - 1) / 2);
      return { rc, rxMax, rxAtt: Math.round(rxAtt) };
    }

    // Genera le terzine simmetriche con doppia distanza Δ: {x, x+Δ, x+2Δ}
    function generaTerzineDoppieDistanze(doppie) {
      const set = new Map();
      doppie.forEach(dd => {
        for (let x = 1; x <= 90; x++) {
          const t = [fuori90(x), fuori90(x + dd.d), fuori90(x + 2 * dd.d)].sort((a, b) => a - b);
          if (new Set(t).size !== 3) continue;   // esclude Δ=45 (numeri coincidenti)
          const key = t.join('-');
          if (!set.has(key)) set.set(key, { t, d: dd.d });
        }
      });
      return [...set.values()];
    }

    function runRicercaDistanze() {
      if (!archivio || !archivio.righe.length) { showNotification('⚠️ Carica prima l\'archivio'); return; }
      const ruotaSorgente = document.getElementById('distRuotaSorgente').value;
      const selTarget = document.getElementById('distRuotaTarget').value;
      const inputNum = document.getElementById('distNumeriSorgente').value.trim();
      if (!inputNum) { showNotification('⚠️ Inserisci i numeri dell\'estrazione sorgente'); return; }
      const numeriSorgente = [...new Set(inputNum.split(/[\s,;]+/).map(Number).filter(n => n >= 1 && n <= 90))];
      if (numeriSorgente.length < 3) { showNotification('⚠️ Servono almeno 3 numeri sorgente'); return; }

      // 1) Distanze e doppie distanze dell'estrazione sorgente
      const distanze = calcolaDistanzeCiclometriche(numeriSorgente);
      const doppie = trovaDoppieDistanze(distanze);
      let htmlDist = `<div class="dist-result"><div style="font-size:11px;color:#c9b98f;margin-bottom:6px;">📐 Distanze ciclometriche di ${numeriSorgente.join('-')} su ${CODICE_TO_NOME[ruotaSorgente]}:</div><div class="dist-distanze">`;
      distanze.forEach(d => {
        const dd = doppie.find(x => x.d === d.d);
        htmlDist += `<span class="dist-tag ${dd ? 'double' : ''}" title="${d.n1}-${d.n2}">Δ${d.d}${dd ? ' ×' + dd.count : ''}</span>`;
      });
      htmlDist += `</div>`;
      if (!doppie.length) {
        htmlDist += `<div style="color:#e0a0a0;font-size:12px;margin-top:6px;">⚠️ Nessuna distanza si ripete: la ricerca richiede almeno una doppia distanza.</div></div>`;
        document.getElementById('distDistanze').innerHTML = htmlDist;
        document.getElementById('distResults').innerHTML = '';
        DIST_FOUND = [];
        return;
      }
      htmlDist += `<div style="font-size:11px;color:#e0b04a;margin-top:6px;">🎯 Doppie distanze target: <b>${doppie.map(d => 'Δ' + d.d).join(', ')}</b> → terzine simmetriche {x, x+Δ, x+2Δ}</div></div>`;
      document.getElementById('distDistanze').innerHTML = htmlDist;

      // 2) Ricerca sulle ruote selezionate (fissa o tutte)
      const ruote = selTarget === 'all' ? Object.keys(RUOTE_NOME) : [selTarget];
      DIST_RUOTE_MONITOR = ruote;
      const candidati = generaTerzineDoppieDistanze(doppie);
      const risultati = [];
      ruote.forEach(rw => {
        const sub = subRuota(rw);
        if (sub.length < 50) return;
        candidati.forEach(c => {
          const s = statisticheAmbo(c.t, sub);
          if (s.rc >= s.rxAtt) risultati.push({ t: c.t, d: c.d, ruota: rw, rc: s.rc, rxAtt: s.rxAtt, rxMax: s.rxMax, rapp: s.rxAtt ? s.rc / s.rxAtt : 0 });
        });
      });
      risultati.sort((a, b) => b.rapp - a.rapp || b.rc - a.rc);
      DIST_FOUND = risultati.slice(0, 60);

      // 3) Render risultati + pulsanti workflow
      let html = `<div class="dist-result"><div style="font-size:11px;color:#c9b98f;margin-bottom:6px;">🧩 Terzine simmetriche con doppie distanze ${doppie.map(d => 'Δ' + d.d).join(', ')} · candidate ${candidati.length} · filtro Rc ≥ Rx(attesa) · trovate <b style="color:#4fae7a;">${risultati.length}</b>${selTarget === 'all' ? ' su tutte le ruote' : ' su ' + CODICE_TO_NOME[selTarget]}</div>`;
      if (!risultati.length) html += `<div class="shrink-line">Nessuna terzina soddisfa Rc ≥ Rx in questo momento</div>`;
      else risultati.slice(0, 30).forEach((r, i) => {
        html += `<div class="shrink-line" style="cursor:pointer;" data-tidx="${i}" title="Clicca per evidenziare sul Cerchio"><b style="color:var(--gold-fill);">${r.t.join(' · ')}</b> <span style="color:#7fd4d4;">[${r.ruota}]</span> Δ${r.d} · Rc <b>${r.rc}</b> / Rx att. <b>${r.rxAtt}</b> (max ${r.rxMax}) · <span class="ok">✓</span> ${r.rapp.toFixed(2)}</div>`;
      });
      html += `<div class="btn-row" style="margin-top:8px;"><button class="step" id="distToWheel">🎯 Inserisci nel Cerchio</button><button class="step" id="distSviluppa">🧩 Sviluppa a gruppi di terzine + monitor Rc≥Rx</button></div></div>`;
      document.getElementById('distResults').innerHTML = html;

      document.querySelectorAll('#distResults .shrink-line[data-tidx]').forEach(elx => {
        elx.addEventListener('click', () => {
          const r = DIST_FOUND[+elx.dataset.tidx];
          if (!r) return;
          r.t.forEach(n => highlighted.add(n));
          renderChips(); renderAll(); aggiornaCadenzeDecine();
          showNotification(`🎯 Terzina ${r.t.join('-')} evidenziata sul Cerchio`);
        });
      });
      const b1 = document.getElementById('distToWheel');
      if (b1) b1.addEventListener('click', () => {
        const uni = [...new Set(DIST_FOUND.slice(0, 12).flatMap(r => r.t))];
        if (!uni.length) { showNotification('⚠️ Nessun risultato da inserire'); return; }
        uni.forEach(n => highlighted.add(n));
        renderChips(); renderAll(); aggiornaCadenzeDecine();
        showNotification(`🎯 ${uni.length} numeri inseriti nel Cerchio`);
      });
      const b2 = document.getElementById('distSviluppa');
      if (b2) b2.addEventListener('click', sviluppaGruppiTerzineMonitor);
      showNotification(`🔍 ${risultati.length} terzine con Rc ≥ Rx trovate`);
    }

    // Sviluppo dei numeri trovati a gruppi di terzine + monitor Rc ≥ Rx per ruota
    function sviluppaGruppiTerzineMonitor() {
      if (!DIST_FOUND.length) { showNotification('⚠️ Prima esegui una ricerca con risultati'); return; }
      const ruote = DIST_RUOTE_MONITOR.length ? DIST_RUOTE_MONITOR : [document.getElementById('distRuotaTarget').value];
      const uniSet = new Set();
      for (const r of DIST_FOUND) {
        const nuovi = r.t.filter(n => !uniSet.has(n));
        if (uniSet.size + nuovi.length > 12) continue;   // max 12 numeri → max 220 terzine
        nuovi.forEach(n => uniSet.add(n));
      }
      const uni = [...uniSet].sort((a, b) => a - b);
      if (uni.length < 3) { showNotification('⚠️ Servono almeno 3 numeri trovati'); return; }
      const terzine = [];
      for (let i = 0; i < uni.length; i++)
        for (let j = i + 1; j < uni.length; j++)
          for (let k = j + 1; k < uni.length; k++)
            terzine.push([uni[i], uni[j], uni[k]]);

      let html = `<div class="dist-result"><div style="font-size:11px;color:#c9b98f;margin-bottom:6px;">🧩 Sviluppo a gruppi di terzine dai ${uni.length} numeri trovati (${uni.join(', ')}) · ${terzine.length} terzine · monitor Rc ≥ Rx(attesa) per ruota</div>`;
      let tot = 0;
      ruote.forEach(rw => {
        const sub = subRuota(rw);
        if (sub.length < 50) return;
        const ok = [];
        terzine.forEach(t => {
          const s = statisticheAmbo(t, sub);
          if (s.rc >= s.rxAtt) ok.push({ t, rc: s.rc, rxAtt: s.rxAtt, rxMax: s.rxMax });
        });
        tot += ok.length;
        ok.sort((a, b) => b.rc - a.rc);
        html += `<div style="margin:8px 0 3px;color:#7fd4d4;font-weight:600;font-size:11px;">${CODICE_TO_NOME[rw]} (${rw}) — ${ok.length} terzine Rc ≥ Rx</div>`;
        if (!ok.length) html += `<div class="shrink-line">— nessuna —</div>`;
        ok.slice(0, 10).forEach(o => {
          html += `<div class="shrink-line" style="cursor:pointer;" data-mon="${o.t.join(',')}" title="Clicca per evidenziare"><b style="color:var(--gold-fill);">${o.t.join(' · ')}</b> · Rc <b>${o.rc}</b> / Rx att. <b>${o.rxAtt}</b> (max ${o.rxMax})</div>`;
        });
      });
      html += `<div style="font-size:11px;color:#c9b98f;margin-top:6px;">Totale terzine monitorate: <b>${tot}</b></div></div>`;
      document.getElementById('distResults').insertAdjacentHTML('beforeend', html);
      document.querySelectorAll('#distResults .shrink-line[data-mon]').forEach(elx => {
        elx.addEventListener('click', () => {
          elx.dataset.mon.split(',').map(Number).forEach(n => highlighted.add(n));
          renderChips(); renderAll(); aggiornaCadenzeDecine();
          showNotification('🎯 Terzina evidenziata sul Cerchio');
        });
      });
      showNotification(`🧩 Monitor avviato su ${ruote.length} ruota/e`);
    }

    function popolaDistRuote() {
      const opts = Object.entries(RUOTE_NOME).map(([k, v]) => `<option value="${k}">${k} - ${v}</option>`).join('');
      document.getElementById('distRuotaSorgente').innerHTML = opts;
      document.getElementById('distRuotaTarget').innerHTML = '<option value="all">🌐 Tutte le ruote</option>' + opts;
    }
    
    function renderL38Tracker() {
      const box = document.getElementById('l38Tracker');
      const list = JSON.parse(localStorage.getItem('l38preds') || '[]');
      const ruota = document.getElementById('l38Ruota').value;
      const mine = list.map((p, i) => ({ ...p, _i: i })).filter(p => p.ruota === ruota);
      if (!mine.length) { box.innerHTML = ''; return; }
      let h = `<div style="margin-top:8px;font-size:11px;color:#c9b98f;display:flex;justify-content:space-between;align-items:center;">
        <span>📌 Previsioni ${CODICE_TO_NOME[ruota]} (verifica ultime 9):</span>
        <button class="step danger" id="l38ClearWheel" style="flex:0 0 auto;padding:3px 8px;font-size:10px;">️ Tutte</button>
      </div>`;
      mine.forEach(p => {
        let hits = 0, played = 0;
        if (archivio) {
          const sub = subRuota(p.ruota);
          const last = sub.slice(-9);
          played = last.length;
          last.forEach(d => {
            const arr = [d.n1, d.n2, d.n3, d.n4, d.n5];
            p.ambi.forEach(pr => { if (arr.includes(pr[0]) && arr.includes(pr[1])) hits++; });
          });
        }
        h += `<div class="shrink-line" style="display:flex;align-items:center;gap:6px;">
          <span style="flex:1;">n.${p.num} cad.${p.cad} · ${p.ambi.map(a => a.join('-')).join(', ')} · hit <b class="${hits ? 'ok' : ''}">${hits}</b>/${played}</span>
          <button class="step danger l38-del" data-idx="${p._i}" style="flex:0 0 auto;padding:2px 7px;font-size:10px;">✕</button>
        </div>`;
      });
      box.innerHTML = h;
      box.querySelectorAll('.l38-del').forEach(b => b.addEventListener('click', () => {
        const lst = JSON.parse(localStorage.getItem('l38preds') || '[]');
        lst.splice(+b.dataset.idx, 1);
        localStorage.setItem('l38preds', JSON.stringify(lst));
        renderL38Tracker();
        showNotification('🗑️ Previsione eliminata');
      }));
      const cw = box.querySelector('#l38ClearWheel');
      if (cw) cw.addEventListener('click', () => {
        if (!confirm(`Cancellare tutte le previsioni di ${CODICE_TO_NOME[ruota]}?`)) return;
        const lst = JSON.parse(localStorage.getItem('l38preds') || '[]').filter(p => p.ruota !== ruota);
        localStorage.setItem('l38preds', JSON.stringify(lst));
        renderL38Tracker();
        showNotification(`️ Previsioni ${CODICE_TO_NOME[ruota]} cancellate`);
      });
    }
  
    // ============================================================
    // v2.9.1: Verifica estrazioni per periodo
    // ============================================================
    function setupVerDateRange(){
      const dal = document.getElementById('verDataDal');
      const al = document.getElementById('verDataAl');
      if(archivio && archivio.righe.length){
        const dates = archivio.righe.map(r=>parseData(r.data)).filter(Boolean).sort((a,b)=>a-b);
        dal.min = toISOData(dates[0]);
        dal.max = toISOData(dates[dates.length-1]);
        al.min = toISOData(dates[0]);
        al.max = toISOData(dates[dates.length-1]);
        if(!dal.value) dal.value = toISOData(dates[0]);
        if(!al.value) al.value = toISOData(dates[dates.length-1]);
      }
    }

    function verificaEstrazioniPeriodo(){
      if(!archivio || !archivio.righe.length){
        showNotification('⚠️ Carica l\'archivio');
        return;
      }
      const ruota = document.getElementById('l38Ruota').value;
      const dal = document.getElementById('verDataDal').value;
      const al = document.getElementById('verDataAl').value;
      
      if(!dal || !al){
        showNotification('️ Inserisci entrambe le date');
        return;
      }
      
      const dDal = parseData(dal);
      const dAl = parseData(al);
      
      if(dDal > dAl){
        showNotification('️ La data "Dal" deve essere precedente a "Al"');
        return;
      }
      
      // Filtra estrazioni della ruota nel periodo
      const sub = archivio.righe.filter(r=>{
        const d = parseData(r.data);
        return r.ruota===ruota && d>=dDal && d<=dAl;
      }).sort((a,b)=>parseData(a.data)-parseData(b.data));
      
      const conteggio = sub.length;
      const prima = sub.length ? sub[0].data : '—';
      const ultima = sub.length ? sub[sub.length-1].data : '—';
      
      let html = `<div style="padding:10px;background:rgba(79,174,122,.1);border-radius:8px;">
        <div style="color:#4fae7a;font-weight:600;margin-bottom:6px;">
          📊 ${CODICE_TO_NOME[ruota]} — dal ${dal} al ${al}
        </div>
        <div style="margin-bottom:4px;">
          <b>Estrazioni totali:</b> <span style="color:var(--gold-fill);font-size:16px;">${conteggio}</span>
        </div>
        <div style="font-size:11px;color:#b3a37e;">
          Prima: ${prima} · Ultima: ${ultima}
        </div>
        ${conteggio<50 ? '<div style="margin-top:6px;color:#e0a0a0;font-size:11px;">️ Meno di 50 estrazioni: risultati poco significativi</div>' : ''}
      </div>`;
      
      document.getElementById('verRisultato').innerHTML = html;
      showNotification(`📊 ${conteggio} estrazioni trovate`);
    }
    
    // ============================================================
    // STORICO & CONVERGENZE L2
    // ============================================================
    const RUOTE_L2 = ['Bari', 'Cagliari', 'Firenze', 'Genova', 'Milano', 'Napoli', 'Palermo', 'Roma', 'Torino', 'Venezia', 'Nazionale'];

    function parseHistory(text) {
      const draws = [];
      text.split(/\n+/).map(l => l.trim()).filter(Boolean).forEach(line => {
        let ruota = null;
        for (const r of RUOTE_L2) { if (line.toLowerCase().includes(r.toLowerCase())) { ruota = r; break; } }
        const nums = [...new Set((line.match(/\d{1,2}/g) || []).map(Number).filter(n => n >= 1 && n <= 90))];
        if (nums.length) draws.push({ ruota, numeri: nums.slice(0, 5) });
      });
      return draws;
    }

    function loadHistoryFromTextarea() {
      const draws = parseHistory(document.getElementById('historyInput').value);
      if (!draws.length) { showNotification('⚠️ Nessuna estrazione valida'); return; }
      draws.forEach(d => HISTORY.push(d));
      document.getElementById('historyInput').value = '';
      renderHistList(); updateRuotaFilter();
      showNotification(`📥 ${draws.length} estrazioni caricate`);
    }

    function renderHistList() {
      const box = document.getElementById('histList');
      box.innerHTML = HISTORY.map((d, i) => `
        <div class="hist-item">
          <span class="ruota">${d.ruota || '—'}</span>
          <span class="nums">${d.numeri.join(' ')}</span>
          ${d.data ? `<span style="font-size:10px;color:#8a7a5c;white-space:nowrap;">${d.data}</span>` : ''}
          <span class="del" data-i="${i}">✕</span>
        </div>`).join('');
      box.querySelectorAll('.del').forEach(x => {
        x.addEventListener('click', () => { HISTORY.splice(parseInt(x.dataset.i), 1); renderHistList(); updateRuotaFilter(); });
      });
    }

    function updateRuotaFilter() {
      const sel = document.getElementById('ruotaFilter');
      const ruote = [...new Set(HISTORY.map(d => d.ruota).filter(Boolean))];
      sel.innerHTML = '<option value="all">Tutte le ruote</option>' + ruote.map(r => `<option value="${r}">${r}</option>`).join('');
    }

    function runConvergence() {
      const filter = document.getElementById('ruotaFilter').value;
      const draws = filter === 'all' ? HISTORY : HISTORY.filter(d => d.ruota === filter);
      if (draws.length < 2) { showNotification('⚠️ Servono almeno 2 estrazioni'); return; }
      const conv = new Map();
      draws.forEach((draw, di) => {
        computeCandidates(draw.numeri).forEach(c => {
          if (!conv.has(c.n)) conv.set(c.n, { n: c.n, drawCount: 0, score: 0, motivi: [], ruote: new Set() });
          const e = conv.get(c.n);
          e.drawCount++; e.score += c.score;
          e.motivi.push(`estr.${di + 1}: ${c.motivi[0]}`);
          if (draw.ruota) e.ruote.add(draw.ruota);
        });
      });
      CONVERGENCE = [...conv.values()].filter(e => e.drawCount >= 2).sort((a, b) => b.drawCount - a.drawCount || b.score - a.score);
      renderConvergencePanel(draws.length);
      showNotification(`🔀 ${CONVERGENCE.length} convergenti su ${draws.length} estrazioni`);
      renderNumeriPromettenti();
    }

    function renderConvergencePanel(totalDraws) {
      const box = document.getElementById('convResults');
      if (!CONVERGENCE.length) { box.innerHTML = '<div style="text-align:center;color:#8a7a5c;font-size:12px;padding:8px;">Nessuna convergenza</div>'; return; }
      box.innerHTML = `<div style="font-size:12px;color:#c9b98f;margin:8px 0 6px;">Numeri "forti": candidati da più estrazioni</div>` +
        CONVERGENCE.slice(0, 12).map(e => `<div class="conv-item" data-n="${e.n}"><span class="cn">${e.n}</span><span class="conv-badge">in ${e.drawCount}/${totalDraws}</span><span class="cm">${[...e.ruote].join(', ') || '—'}</span><span class="cs">${e.score.toFixed(1)}</span></div>`).join('') +
        `<button class="step" id="convHighlight" style="margin-top:6px;">🎯 Evidenzia i convergenti</button>`;
      box.querySelectorAll('.conv-item').forEach(it => {
        it.addEventListener('click', () => { highlighted.add(parseInt(it.dataset.n)); renderChips(); renderAll(); aggiornaCadenzeDecine(); });
      });
      document.getElementById('convHighlight').addEventListener('click', () => {
        CONVERGENCE.slice(0, 8).forEach(e => highlighted.add(e.n));
        renderChips(); renderAll(); aggiornaCadenzeDecine();
        showNotification('🎯 Convergenti evidenziati');
      });
    }

    function loadHistoryDemo() {
      document.getElementById('historyInput').value = `Bari 7 23 45 68 81
Napoli 12 34 56 72 90
Milano 3 18 27 44 62
Bari 15 30 60 75 88
Roma 9 27 45 54 72`;
      loadHistoryFromTextarea();
    }

    function clearHistory() {
      HISTORY.length = 0; CONVERGENCE = [];
      renderHistList(); updateRuotaFilter();
      document.getElementById('convResults').innerHTML = '';
    }

    function popolaConvArchivioRuote() {
      document.getElementById('convArchivioRuota').innerHTML =
        '<option value="all">🌐 Tutte le ruote</option>' +
        Object.entries(RUOTE_NOME).map(([k, v]) => `<option value="${k}">${k} - ${v}</option>`).join('');
    }

    function caricaRuoteDaArchivioPerConvergenze() {
      if (!archivio || !archivio.righe.length) { showNotification('⚠️ Prima scarica o carica l\'archivio'); return; }
      const codiceRuota = document.getElementById('convArchivioRuota').value;
      const nEstrazioni = parseInt(document.getElementById('convArchivioN').value) || 10;
      let righeFiltrate;
      if (codiceRuota === 'all') righeFiltrate = archivio.righe;
      else righeFiltrate = archivio.righe.filter(r => r.ruota === codiceRuota);
      if (!righeFiltrate.length) { showNotification('⚠️ Nessuna estrazione trovata'); return; }
      const perRuota = {};
      righeFiltrate.forEach(r => { if (!perRuota[r.ruota]) perRuota[r.ruota] = []; perRuota[r.ruota].push(r); });
      let aggiunte = 0, ruoteCaricate = 0;
      for (const [codice, righe] of Object.entries(perRuota)) {
        righe.sort((a, b) => parseData(a.data) - parseData(b.data));
        const ultime = righe.slice(-nEstrazioni);
        const nomeRuota = CODICE_TO_NOME[codice];
        ultime.forEach(r => {
          const esiste = HISTORY.some(h => h.ruota === nomeRuota && h.data === r.data);
          if (!esiste) {
            HISTORY.push({ ruota: nomeRuota, numeri: [r.n1, r.n2, r.n3, r.n4, r.n5], data: r.data });
            aggiunte++;
          }
        });
        ruoteCaricate++;
      }
      renderHistList();
      updateRuotaFilter();
      showNotification(` ${aggiunte} estrazioni da ${ruoteCaricate} ruote caricate nelle Convergenze`);
      if (HISTORY.length >= 2) runConvergence();
    }

    // ============================================================
    // ALLINEAMENTI
    // ============================================================
    function alignmentScoreFor(offsets, mode) {
      const dirs = { N: offsets[2] + HALF, E: offsets[2] + HALF + 90, S: offsets[2] + HALF + 180, O: offsets[2] + HALF + 270 };
      let score = 0;
      for (let r = 0; r < 3; r++) for (const d in dirs) {
        const rel = normalizeDeg(normalizeDeg(dirs[d]) - offsets[r]);
        let idx = Math.round(rel / SECTOR_ANGLE - 0.5);
        idx = ((idx % SECTORS) + SECTORS) % SECTORS;
        const num = RINGS[r].numbers[idx];
        if (mode !== 'verde' && highlighted.has(num)) score += 1;
        if (mode !== 'oro' && drawHighlighted.has(num)) score += (mode === 'entrambi' ? 0.5 : 1);
      }
      return score;
    }

    function findBestAlignments() {
      const mode = document.getElementById('alignMode').value;
      if (!highlighted.size && !drawHighlighted.size) { showNotification('⚠️ Evidenzia prima almeno un numero'); return; }
      document.getElementById('alignmentResult').innerHTML = '<span> Ricerca in corso...</span>';
      setTimeout(() => {
        const results = [];
        let minScore = -1;
        for (let o0 = 0; o0 < 360; o0 += SECTOR_ANGLE)
          for (let o1 = 0; o1 < 360; o1 += SECTOR_ANGLE)
            for (let o2 = 0; o2 < 360; o2 += SECTOR_ANGLE) {
              const t = [o0 + REST_OFFSET, o1 + REST_OFFSET, o2 + REST_OFFSET];
              const s = alignmentScoreFor(t, mode);
              if (results.length < 5) {
                results.push({ offsets: t, score: s });
                if (results.length === 5) { results.sort((a, b) => b.score - a.score); minScore = results[4].score; }
              } else if (s > minScore) {
                results[4] = { offsets: t, score: s };
                results.sort((a, b) => b.score - a.score);
                minScore = results[4].score;
              }
            }
        results.sort((a, b) => b.score - a.score);
        renderAlignTopList(results, mode);
      }, 30);
    }

    function renderAlignTopList(results, mode) {
      const out = document.getElementById('alignmentResult');
      const box = document.getElementById('alignTopList');
      if (!results[0] || results[0].score <= 0) {
        out.innerHTML = '<span style="color:#e0a0a0;">Nessun allineamento possibile con i numeri attuali</span>';
        box.innerHTML = '';
        return;
      }
      out.innerHTML = `<span>Trovate <b>${results.length}</b> configurazioni — scegline una</span>`;
      box.innerHTML = results.map((r, i) => `
        <div style="display:flex;align-items:center;gap:8px;padding:6px 8px;background:rgba(0,0,0,.15);border-radius:6px;margin-top:6px;font-size:12.5px;">
          <span style="color:var(--brass-hi);font-weight:700;">#${i + 1}</span>
          <span style="flex:1;color:#c9b98f;">Punteggio <b style="color:#4fae7a;">${r.score.toFixed(1)}</b></span>
          <button class="step" data-idx="${i}" style="flex:0 0 auto;padding:6px 12px;">Applica</button>
        </div>`).join('');
      box.querySelectorAll('button[data-idx]').forEach(btn => {
        btn.addEventListener('click', () => {
          const i = parseInt(btn.dataset.idx);
          offset[0] = results[i].offsets[0]; offset[1] = results[i].offsets[1]; offset[2] = results[i].offsets[2];
          renderAll();
          out.innerHTML = `<span><strong>#${i + 1} applicata — Punteggio ${results[i].score.toFixed(1)}</strong></span>`;
          renderAlignDetail();
          showNotification(`✨ Allineamento applicato (score ${results[i].score.toFixed(1)})`);
        });
      });
    }

    function renderAlignDetail() {
      const dirs = compassAngles();
      const nomi = ['Interno', 'Medio', 'Esterno'];
      let html = '<div style="margin-top:8px;font-size:12px;">';
      html += '<div style="color:var(--brass-hi);font-weight:600;margin-bottom:4px;">Numeri sui punti cardinali:</div>';
      let found = 0;
      for (const d in dirs) for (let r = 0; r < 3; r++) {
        const v = valueAt(r, dirs[d]);
        if (v === null) continue;
        const isOro = highlighted.has(v), isVerde = drawHighlighted.has(v);
        if (isOro || isVerde) {
          const badges = [isOro ? '🟡' : '', isVerde ? '🟢' : ''].filter(Boolean).join('');
          html += `<div style="padding:3px 6px;background:rgba(79,174,122,.12);border-radius:4px;margin-bottom:3px;">
            <b style="color:var(--gold-fill);">${d}</b> · ${nomi[r]} · <b>${v}</b> ${badges}
          </div>`;
          found++;
        }
      }
      if (!found) html += '<div style="color:#8a7a5c;">Nessun numero evidenziato sui cardinali</div>';
      html += '</div>';
      document.getElementById('alignDetail').innerHTML = html;
    }

    // ============================================================
    // EVIDENZIAZIONI & CHIP
    // ============================================================
    function toggleHighlight(n) {
      if (highlighted.has(n)) highlighted.delete(n); else highlighted.add(n);
      renderChips(); renderAll(); aggiornaCadenzeDecine();
    }

    function renderChips() {
      const box = document.getElementById('chips');
      box.innerHTML = [...highlighted].sort((a, b) => a - b).map(n => `<span class="chip" data-n="${n}">${n} ✕</span>`).join('');
      box.querySelectorAll('.chip').forEach(chip => {
        chip.addEventListener('click', () => { highlighted.delete(parseInt(chip.dataset.n)); renderChips(); renderAll(); aggiornaCadenzeDecine(); });
      });
    }

    function renderDrawChips() {
      const box = document.getElementById('drawChips');
      box.innerHTML = [...drawHighlighted].sort((a, b) => a - b).map(n => `<span class="chip draw-chip" data-n="${n}">${n} ✕</span>`).join('');
      box.querySelectorAll('.chip').forEach(chip => {
        chip.addEventListener('click', () => { drawHighlighted.delete(parseInt(chip.dataset.n)); renderDrawChips(); renderAll(); aggiornaCadenzeDecine(); });
      });
      if (drawHighlighted.size >= 2) {
        analizzaCoerenzaEstrazioneUI([...drawHighlighted]);
      } else {
        document.getElementById('coerenzaBox').innerHTML = '';
      }
    }

    function parseDrawNumbers(text) {
      return [...new Set((text.match(/\d{1,2}/g) || []).map(s => parseInt(s)).filter(n => n >= 1 && n <= 90))];
    }

    function renderAll() { renderRing(0); renderRing(1); renderRing(2); }

    // ============================================================
    // CONFIGURAZIONI / CONDIVISIONE / TEMA
    // ============================================================
    function saveConfiguration() {
      const nameInput = document.getElementById('configNameInput');
      const name = nameInput.value.trim() || `Config ${new Date().toLocaleString('it-IT')}`;
      const config = { name, offsets: [...offset], highlighted: [...highlighted], drawHighlighted: [...drawHighlighted] };
      const saved = JSON.parse(localStorage.getItem('cerchioConfigs') || '[]');
      saved.push(config);
      localStorage.setItem('cerchioConfigs', JSON.stringify(saved));
      nameInput.value = '';
      updateSavedList();
      showNotification(`💾 "${name}" salvata`);
    }

    function loadConfiguration(i) {
      const saved = JSON.parse(localStorage.getItem('cerchioConfigs') || '[]');
      const c = saved[i]; if (!c) return;
      offset[0] = c.offsets[0]; offset[1] = c.offsets[1]; offset[2] = c.offsets[2];
      highlighted.clear(); c.highlighted.forEach(n => highlighted.add(n));
      drawHighlighted.clear(); c.drawHighlighted.forEach(n => drawHighlighted.add(n));
      renderAll(); aggiornaCadenzeDecine();
      showNotification(` Caricata: ${c.name}`);
    }

    function deleteConfiguration(i) {
      const saved = JSON.parse(localStorage.getItem('cerchioConfigs') || '[]');
      saved.splice(i, 1);
      localStorage.setItem('cerchioConfigs', JSON.stringify(saved));
      updateSavedList();
    }

    function updateSavedList() {
      const list = document.getElementById('savedList');
      const saved = JSON.parse(localStorage.getItem('cerchioConfigs') || '[]');
      if (!saved.length) { list.innerHTML = '<div style="text-align:center;color:#8a7a5c;font-size:12px;padding:8px;">Nessuna configurazione</div>'; return; }
      list.innerHTML = saved.map((c, i) => `<div class="saved-item"><span class="name" onclick="loadConfiguration(${i})">${c.name}</span><span class="del" onclick="deleteConfiguration(${i})">✕</span></div>`).join('');
    }

    function generateShareLink() {
      const params = new URLSearchParams();
      params.set('o', offset.map(o => o.toFixed(2)).join(','));
      params.set('h', [...highlighted].join(','));
      params.set('d', [...drawHighlighted].join(','));
      navigator.clipboard.writeText(`${location.origin}${location.pathname}?${params}`).then(() => showNotification('🔗 Link copiato')).catch(() => prompt('Copia:', `${location.origin}${location.pathname}?${params}`));
    }

    function loadFromURL() {
      const params = new URLSearchParams(location.search);
      if (params.has('o')) {
        const offs = params.get('o').split(',').map(Number);
        if (offs.length === 3) { offset[0] = offs[0]; offset[1] = offs[1]; offset[2] = offs[2]; }
      }
      if (params.has('h')) params.get('h').split(',').forEach(n => { n = parseInt(n); if (n >= 1 && n <= 90) highlighted.add(n); });
      if (params.has('d')) params.get('d').split(',').forEach(n => { n = parseInt(n); if (n >= 1 && n <= 90) drawHighlighted.add(n); });
    }

    function toggleTheme() {
      const html = document.documentElement;
      const next = html.getAttribute('data-theme') === 'light' ? 'dark' : 'light';
      html.setAttribute('data-theme', next);
      localStorage.setItem('theme', next);
      document.getElementById('themeToggle').textContent = next === 'dark' ? '🌙 Tema' : '☀️ Tema';
    }

    function initTheme() {
      const saved = localStorage.getItem('theme');
      if (saved) { document.documentElement.setAttribute('data-theme', saved); document.getElementById('themeToggle').textContent = saved === 'dark' ? ' Tema' : '☀️ Tema'; }
    }

    function doFind() {
      const input = document.getElementById('finderInput');
      const n = parseInt(input.value);
      if (!n || n < 1 || n > 90) return;
      highlighted.add(n);
      renderChips(); renderAll(); aggiornaCadenzeDecine();
      input.select();
    }

    // ============================================================
    // EVENT LISTENERS
    // ============================================================
    document.querySelectorAll('button.step[data-ring]').forEach(btn => {
      btn.addEventListener('click', () => stepRing(parseInt(btn.dataset.ring), parseInt(btn.dataset.dir)));
    });
    document.getElementById('resetBtn').addEventListener('click', () => { offset[0] = offset[1] = offset[2] = REST_OFFSET; renderAll(); });
    document.getElementById('clearHighlights').addEventListener('click', () => { highlighted.clear(); renderChips(); renderAll(); aggiornaCadenzeDecine(); });
    document.getElementById('finderGo').addEventListener('click', doFind);
    document.getElementById('finderInput').addEventListener('keydown', e => { if (e.key === 'Enter') doFind(); });
    document.getElementById('drawGo').addEventListener('click', () => {
      parseDrawNumbers(document.getElementById('drawInput').value).forEach(n => drawHighlighted.add(n));
      renderDrawChips(); renderAll(); aggiornaCadenzeDecine();
    });
    document.getElementById('drawClear').addEventListener('click', () => {
      drawHighlighted.clear();
      document.getElementById('drawInput').value = '';
      document.getElementById('coerenzaBox').innerHTML = '';
      renderDrawChips(); renderAll(); aggiornaCadenzeDecine();
    });
    document.getElementById('themeToggle').addEventListener('click', toggleTheme);
    document.getElementById('shareBtn').addEventListener('click', generateShareLink);
    document.getElementById('guideBtn').addEventListener('click', () => document.getElementById('guideModal').classList.add('open'));
    document.getElementById('guideClose').addEventListener('click', () => document.getElementById('guideModal').classList.remove('open'));
    document.getElementById('guideModal').addEventListener('click', e => { if (e.target.id === 'guideModal') e.currentTarget.classList.remove('open'); });
    document.getElementById('clearAllBtn').addEventListener('click', () => {
      if (!confirm('Cancellare tutte le evidenziazioni?')) return;
      highlighted.clear(); drawHighlighted.clear();
      offset[0] = offset[1] = offset[2] = REST_OFFSET;
      clearEngine(); clearHistory(); LAST_BACKTEST = []; MANNA_RESULTS = []; PIRAMIDE_RESULTS = [];
      L38.prevHl = []; L38.ambi = []; L38.esclusi = []; L38.colonne = []; L38.shrinkFinal = [];
      document.getElementById('coerenzaBox').innerHTML = '';
      document.getElementById('l38Cols').innerHTML = '';
      document.getElementById('l38Results').innerHTML = '';
      renderChips(); renderDrawChips(); renderAll(); renderNumeriPromettenti(); aggiornaCadenzeDecine();
      renderL38Tracker();
      showNotification('🗑️ Tutto pulito');
    });
    document.getElementById('importDemoBtn').addEventListener('click', () => {
      [7, 23, 45, 68, 81, 12, 34, 56, 72, 90].forEach(n => drawHighlighted.add(n));
      renderDrawChips(); renderAll(); aggiornaCadenzeDecine();
      showNotification('📥 Demo caricata');
    });
    document.getElementById('statsRun').addEventListener('click', mostraStatisticheTop10);
    document.getElementById('statsTop10').addEventListener('click', mostraStatisticheTop10);
    document.getElementById('statsPattern').addEventListener('click', mostraStatistichePattern);
    document.getElementById('btnLoadLocal').addEventListener('click', () => { document.getElementById('fileInput').click(); });
    document.getElementById('fileInput').addEventListener('change', async (e) => {
      const file = e.target.files[0];
      if (!file) return;
      if (!file.name.toLowerCase().endsWith('.zip')) { showNotification('⚠️ Seleziona un file ZIP'); return; }
      await caricaArchivioLocale(file);
      e.target.value = '';
    });
    document.getElementById('btnLoadCache').addEventListener('click', async () => {
      const dati = await caricaCache();
      if (dati) { updateArchiveUI(); setupBtDateRange(); setupL38DateRange(); renderL38Tracker(); showNotification('💾 Cache caricata'); }
    });
    document.getElementById('btnValida').addEventListener('click', validaDati);
    document.getElementById('btnClearCache').addEventListener('click', async () => {
      if (confirm('Cancellare l\'archivio delle estrazioni?')) await cancellaCache();
    });
    document.getElementById('btnUseLast1').addEventListener('click', () => portaEstrazioni(1));
    document.getElementById('btnUseLast3').addEventListener('click', () => portaEstrazioni(3));
    document.getElementById('btRun').addEventListener('click', runBacktest);
    document.getElementById('esitiRefresh').addEventListener('click', renderNumeriPromettenti);
    document.getElementById('engineRun').addEventListener('click', runEngine);
    document.getElementById('heatToggle').addEventListener('click', toggleHeat);
    document.getElementById('tourBtn').addEventListener('click', startTour);
    document.getElementById('engineClear').addEventListener('click', clearEngine);
    document.getElementById('histLoad').addEventListener('click', loadHistoryFromTextarea);
    document.getElementById('histDemo').addEventListener('click', loadHistoryDemo);
    document.getElementById('histClear').addEventListener('click', clearHistory);
    document.getElementById('convRun').addEventListener('click', runConvergence);
    document.getElementById('btnCaricaRuoteConv').addEventListener('click', caricaRuoteDaArchivioPerConvergenze);
    document.getElementById('findAlignmentBtn').addEventListener('click', findBestAlignments);
    document.getElementById('saveConfigBtn').addEventListener('click', saveConfiguration);
    document.getElementById('mannaRun').addEventListener('click', runManna);
    document.getElementById('piramideRun').addEventListener('click', runPiramide);
    document.getElementById('piramideClear').addEventListener('click', () => {
      PIRAMIDE_RESULTS = [];
      document.getElementById('piramideResults').innerHTML = '';
      showNotification('🧹 Piramide ripulita');
    });
    document.getElementById('spieRun').addEventListener('click', runSpie);
    document.getElementById('figDelayRun').addEventListener('click', runFigureDelay);
    document.getElementById('l38Mode').addEventListener('change', e => {
      document.getElementById('l38Cad').disabled = (e.target.value !== 'manuale');
    });
    document.getElementById('l38Ruota').addEventListener('change', () => renderL38Tracker());
    document.getElementById('l38Run').addEventListener('click', runLogica38);
    document.getElementById('l38Shrink').addEventListener('click', runShrink38);
    document.getElementById('l38Terzine').addEventListener('click', runTerzine280);
    document.getElementById('l38Save').addEventListener('click', saveL38);
    // v2.10: Doppie Distanze
    document.getElementById('distRun').addEventListener('click', runRicercaDistanze);
    // v2.9.1: Verifica periodo
     document.getElementById('verEstrazioni').addEventListener('click', verificaEstrazioniPeriodo);

    // ============================================================
    // INIZIALIZZAZIONE
    // ============================================================
    buildStatic();
    RINGS.forEach((r, i) => buildRing(r, i));
    buildCompass(ringGroups[2]);
    overlayG = el('g', { id: 'figureOverlay' });
    svg.appendChild(overlayG);
    renderAll();
    renderChips();
    renderDrawChips();
    aggiornaCadenzeDecine();
    updateSavedList();
    updateRuotaFilter();
    popolaSelectRuote();
    popolaBtRuote();
    popolaConvArchivioRuote();
    popolaDistRuote();
    initTheme();
    loadFromURL();
    updateArchiveUI();
    setupBtDateRange();
    setupL38DateRange();
    renderL38Tracker();
    setupVerDateRange();
    if (cacheEsiste()) {
      setTimeout(async () => {
        const dati = await caricaCache();
        if (dati) { updateArchiveUI(); setupBtDateRange(); setupL38DateRange(); renderL38Tracker(); }
      }, 300);
    }
  </script>
</body>
</html>


HTML:
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Manuale — Cerchio di Spilimbergo Suite Completa v2.10</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap');
:root {
  --brass: #b4842c;
  --brass-hi: #e0b04a;
  --paper: #f4ecd8;
  --ink: #2c1e12;
  --ink-soft: #5a4630;
  --garnet: #8c2a2a;
  --accent: #0f6e6e;
  --ok: #4fae7a;
  --warn: #e0b04a;
  --err: #e0a0a0;
  --panel: linear-gradient(180deg, #2b1f12, #201709);
  --body: radial-gradient(ellipse at 50% -10%, #2c2013 0%, #1a130b 55%);
}
* { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0;
  background: var(--body);
  color: var(--paper);
  font-family: 'Inter', sans-serif;
  line-height: 1.65;
}
h1, h2, h3, h4 {
  font-family: 'Cormorant Garamond', serif;
  color: var(--brass-hi);
  margin: 0 0 10px;
}
h1 { font-size: clamp(30px, 5vw, 46px); font-weight: 700; }
h2 { font-size: 26px; margin-top: 36px; padding-bottom: 6px; border-bottom: 1px solid #4a3a20; }
h3 { font-size: 20px; margin-top: 24px; color: var(--brass); }
h4 { font-size: 16px; color: var(--brass-hi); margin-top: 18px; }
a { color: var(--brass-hi); text-decoration: underline; }
.wrap {
  max-width: 1000px;
  margin: 0 auto;
  padding: 40px 20px 80px;
}
.hero {
  text-align: center;
  padding: 30px 20px;
  background: var(--panel);
  border: 1px solid #4a3a20;
  border-radius: 16px;
  margin-bottom: 30px;
  box-shadow: 0 20px 50px -20px rgba(0,0,0,.6);
}
.hero .subtitle {
  font-family: 'Cormorant Garamond', serif;
  font-style: italic;
  color: #c9b98f;
  font-size: 18px;
  margin: 8px 0 0;
}
.toc {
  background: var(--panel);
  border: 1px solid #4a3a20;
  border-radius: 12px;
  padding: 20px 26px;
  margin-bottom: 30px;
}
.toc h2 { margin-top: 0; border: none; font-size: 22px; }
.toc ol { columns: 2; column-gap: 40px; padding-left: 20px; }
.toc ol li { margin-bottom: 6px; }
.toc a { color: var(--paper); text-decoration: none; }
.toc a:hover { color: var(--brass-hi); }
section {
  background: var(--panel);
  border: 1px solid #4a3a20;
  border-radius: 12px;
  padding: 22px 26px;
  margin-bottom: 20px;
}
section p, section li { font-size: 15px; }
ul, ol { padding-left: 22px; }
li { margin-bottom: 6px; }
kbd {
  background: #332512;
  border: 1px solid var(--brass);
  border-radius: 4px;
  padding: 1px 6px;
  font-family: 'Courier New', monospace;
  font-size: 12px;
  color: var(--brass-hi);
}
.code {
  background: rgba(0,0,0,.35);
  border-left: 3px solid var(--brass);
  padding: 10px 14px;
  border-radius: 6px;
  font-family: 'Courier New', monospace;
  font-size: 13px;
  color: var(--brass-hi);
  margin: 10px 0;
  overflow-x: auto;
}
.callout {
  padding: 12px 16px;
  border-radius: 8px;
  margin: 14px 0;
  font-size: 14px;
}
.callout.info { background: rgba(15,110,110,.18); border-left: 4px solid var(--accent); }
.callout.warn { background: rgba(224,176,74,.15); border-left: 4px solid var(--warn); color: #f0d89a; }
.callout.err  { background: rgba(224,160,160,.15); border-left: 4px solid var(--err); }
.callout.ok   { background: rgba(79,174,122,.15); border-left: 4px solid var(--ok); }
.callout b { color: var(--brass-hi); }
table {
  width: 100%;
  border-collapse: collapse;
  margin: 12px 0;
  font-size: 14px;
}
table th, table td {
  padding: 8px 10px;
  border-bottom: 1px solid #4a3a20;
  text-align: left;
}
table th { color: var(--brass-hi); font-weight: 600; font-size: 12px; }
table td { color: var(--paper); }
.step-list { counter-reset: step; list-style: none; padding-left: 0; }
.step-list li {
  counter-increment: step;
  padding: 10px 12px 10px 48px;
  background: rgba(0,0,0,.2);
  border-radius: 8px;
  margin-bottom: 8px;
  position: relative;
}
.step-list li::before {
  content: counter(step);
  position: absolute;
  left: 12px; top: 10px;
  width: 26px; height: 26px;
  background: var(--brass);
  color: #1a1206;
  border-radius: 50%;
  text-align: center;
  font-weight: 700;
  font-family: 'Cormorant Garamond', serif;
  font-size: 15px;
  line-height: 26px;
}
.workflow {
  background: rgba(15,110,110,.12);
  border: 1px solid var(--accent);
  border-radius: 10px;
  padding: 16px 20px;
  margin: 14px 0;
}
.workflow h4 { margin-top: 0; color: #7fd4d4; }
.glossary dt {
  font-weight: 600;
  color: var(--brass-hi);
  font-family: 'Cormorant Garamond', serif;
  font-size: 17px;
  margin-top: 12px;
}
.glossary dd { margin: 0 0 10px 18px; color: #c9b98f; }
.back-top {
  position: fixed;
  bottom: 20px; right: 20px;
  background: var(--brass);
  color: #1a1206;
  width: 44px; height: 44px;
  border-radius: 50%;
  text-align: center;
  line-height: 44px;
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 4px 12px rgba(0,0,0,.4);
  font-family: 'Cormorant Garamond', serif;
  font-size: 20px;
}
@media (max-width: 720px) {
  .toc ol { columns: 1; }
  .wrap { padding: 20px 12px 60px; }
  section { padding: 16px; }
}
</style>
</head>
<body>
<div class="wrap">

<div class="hero">
  <h1> Manuale Operativo</h1>
  <div class="subtitle">Cerchio di Spilimbergo — Suite Completa v2.10</div>
  <div style="color:#8a7a5c;font-size:13px;margin-top:10px;">
    Guida passo-passo, consigli metodologici e avvertenze per un uso efficace
  </div>
</div>

<div class="toc">
  <h2>🗂️ Indice</h2>
  <ol>
    <li><a href="#intro">Introduzione e filosofia</a></li>
    <li><a href="#avvio">Primo avvio e prerequisiti</a></li>
    <li><a href="#archivio">Caricamento dell'archivio</a></li>
    <li><a href="#cerchio">Il Cerchio di Spilimbergo</a></li>
    <li><a href="#l38">Logica 38 — Colonne di Cadenza</a></li>
    <li><a href="#distanze">Doppie Distanze v2.10.1</a></li>
    <li><a href="#motore">Motore Previsivo</a></li>
    <li><a href="#convergenze">Convergenze</a></li>
    <li><a href="#backtest">Verifica a Ritroso</a></li>
    <li><a href="#manna">Metodo Manna</a></li>
    <li><a href="#piramide">Piramide Ciclotimica</a></li>
    <li><a href="#spie">Spie per Ruota</a></li>
    <li><a href="#figure">Figure Mancanti</a></li>
    <li><a href="#cadenze">Cadenze & Decine</a></li>
    <li><a href="#allineamenti">Allineamenti</a></li>
    <li><a href="#promettenti">Numeri Promettenti</a></li>
    <li><a href="#statistiche">Statistiche & Pattern</a></li>
    <li><a href="#periodo">Verifica Estrazioni per Periodo</a></li>
    <li><a href="#config">Configurazioni salvabili</a></li>
    <li><a href="#workflow">Workflow operativi consigliati</a></li>
    <li><a href="#avvertenze">Avvertenze e buona pratica</a></li>
    <li><a href="#glossario">Glossario</a></li>
  </ol>
</div>

<!-- 1 -->
<section id="intro">
  <h2>1. Introduzione e filosofia dello strumento</h2>
  <p>Il <b>Cerchio di Spilimbergo</b> è una suite HTML/JS standalone che riunisce in un'unica interfaccia i principali metodi della tradizione lottologica italiana: <b>colonne di cadenza, figure geometriche, convergenze, backtest, Manna, piramide, spie, doppie distanze</b>.</p>

  <h3>Filosofia operativa</h3>
  <ul>
    <li>Ogni modulo è <b>indipendente</b> ma i risultati si <b>aggregano automaticamente</b> nel pannello <b>Numeri Promettenti</b>.</li>
    <li>Il <b>Cerchio</b> è il centro visivo: tutto ciò che evidenzi (oro 🟡) o carichi come estrazione (verde 🟢) si riflette immediatamente sugli anelli.</li>
    <li>Il <b>filtro Rc ≥ Rx</b> è il criterio statistico trasversale: segnala combinazioni "mai state così ritardate" rispetto al loro storico.</li>
  </ul>

  <div class="callout warn">
    <b>⚠️ Premessa etica obbligatoria:</b> il Lotto è un gioco casuale. Nessun metodo ha validità scientifica predittiva. Questo strumento è per <b>studio, ricerca e intrattenimento</b>. Gioca sempre con responsabilità e limiti di spesa prestabiliti.
  </div>
</section>

<!-- 2 -->
<section id="avvio">
  <h2>2. Primo avvio e prerequisiti</h2>

  <h3>Cosa serve</h3>
  <ul>
    <li>Un browser moderno (Chrome, Firefox, Edge, Safari)</li>
    <li>Connessione internet <b>solo al primo caricamento</b> (per il font e JSZip da CDN)</li>
    <li>Il file <code>storico.zip</code> scaricabile da <a href="https://www.brightstarlottery.it/STORICO_ESTRAZIONI_LOTTO/storico.zip" target="_blank">brightstarlottery.it</a></li>
  </ul>

  <h3>Procedura di primo avvio</h3>
  <ol class="step-list">
    <li>Apri il file HTML nel browser (doppio click o drag-and-drop).</li>
    <li>Nella toolbar in alto trovi: <kbd>🌙 Tema</kbd>, <kbd>🔗 Condividi</kbd>, <kbd>📥 Demo</kbd>, <kbd>🗑️ Pulisci Tutto</kbd>, <kbd>❓ Guida</kbd>.</li>
    <li>Clicca <kbd> Demo</kbd> per caricare 10 numeri di esempio e familiarizzare con l'interfaccia.</li>
    <li><b>Prima di qualsiasi analisi seria, carica l'archivio</b> (sezione 3).</li>
  </ol>

  <div class="callout info">
    <b>💡 Salvataggio locale:</b> il programma usa <b>IndexedDB + localStorage</b> per conservare tra sessioni la cache dell'archivio, le previsioni Logica 38, le configurazioni cerchio e il tema scelto.
  </div>
</section>

<!-- 3 -->
<section id="archivio">
  <h2>3. Caricamento dell'archivio estrazioni</h2>

  <div class="callout err">
    <b>️ Passo cruciale.</b> Senza archivio, quasi tutti i moduli statistici (Logica 38, Backtest, Manna, Spie, Doppie Distanze, Figure Mancanti) <b>non funzionano</b>.
  </div>

  <h3>Procedura passo-passo</h3>
  <ol class="step-list">
    <li>Scarica manualmente il file <code>storico.zip</code> dal link indicato nella card Archivio.</li>
    <li>Nella card <b> Archivio Estrazioni</b>, clicca <kbd>📂 Carica ZIP Locale</kbd>.</li>
    <li>Seleziona il file ZIP appena scaricato.</li>
    <li>Attendi la decompressione: vedrai la barra di progresso e il log di avanzamento.</li>
    <li>Al termine, il punto di stato diventa <b>verde</b> e mostra il numero di righe caricate.</li>
  </ol>

  <h3>Cosa succede internamente</h3>
  <ul>
    <li>Il file <code>storico.txt</code> dentro lo ZIP viene parsato riga per riga.</li>
    <li>Ogni riga è validata (5 numeri 1-90, ruota valida, nessun duplicato).</li>
    <li>I dati vengono salvati in <b>IndexedDB</b> (persistente) e in <b>localStorage</b> (meta).</li>
    <li>Le date dei calendar picker vengono impostate automaticamente.</li>
  </ul>

  <h3>Epoca storica</h3>
  <p>Il selettore <b>Epoca storica</b> permette di filtrare:</p>
  <ul>
    <li><b>Archivio Completo</b> — tutto lo storico disponibile</li>
    <li><b>Venus (2005-oggi)</b> — utile per analisi moderne</li>
    <li><b>4 Estrazioni (2023-oggi)</b> — per studi recenti</li>
  </ul>

  <h3>Pulsanti rapidi</h3>
  <ul>
    <li><kbd>🎯 Ultima estrazione</kbd> — porta i 5 numeri dell'ultima estrazione della ruota selezionata sul Cerchio (verdi).</li>
    <li><kbd> Ultime 3</kbd> — stessa cosa per le ultime 3 estrazioni.</li>
    <li><kbd>🔍 Valida</kbd> — report per ruota: numero estrazioni, prima/ultima data, epoca Venus e 4-estrazioni.</li>
  </ul>

  <div class="callout warn">
    <b>⚠️ Attenzione:</b> il caricamento può richiedere 10-30 secondi su archivi completi. Non chiudere il browser durante il caricamento. Se il file ZIP non contiene <code>storico.txt</code>, il caricamento fallisce. La cache resta nel browser: se cambi PC devi ricaricare.
  </div>
</section>

<!-- 4 -->
<section id="cerchio">
  <h2>4. Il Cerchio di Spilimbergo</h2>

  <h3>Struttura</h3>
  <p>Tre anelli concentrici (Interno, Medio, Esterno), ciascuno con <b>30 numeri</b> disposti in 32 settori (2 settori vuoti a Ovest). I 90 numeri sono distribuiti secondo lo schema classico di Spilimbergo.</p>

  <h3>Interazione</h3>
  <table>
    <tr><th>Azione</th><th>Effetto</th></tr>
    <tr><td><b>Click su un numero</b></td><td>Lo evidenzia in <b>oro</b> 🟡 (aggiunge/rimuove)</td></tr>
    <tr><td><b>Drag su un anello</b></td><td>Ruota l'anello; al rilascio scatta al settore più vicino</td></tr>
    <tr><td><b>Pulsanti ↺ / ↻</b></td><td>Ruota di un settore in senso antiorario/orario</td></tr>
    <tr><td><b>Riporta le ghiere a riposo</b></td><td>Resetta tutti gli anelli alla posizione di default</td></tr>
  </table>

  <h3>Punti cardinali</h3>
  <p>La tabella <b>Lettura ai 4 punti cardinali</b> mostra in tempo reale i numeri che si trovano su N, S, E, O per ciascun anello.</p>

  <h3>Evidenziazioni</h3>
  <ul>
    <li><b> Oro</b> = numeri evidenziati manualmente (da te, dal Motore, da Convergenze, ecc.)</li>
    <li><b>🟢 Verde</b> = numeri di un'estrazione caricata (da archivio o da input manuale)</li>
    <li>I chip sotto ogni sezione permettono di rimuovere singoli numeri con un click.</li>
  </ul>

  <h3>Distanze e Figure</h3>
  <p>La card <b>📐 Distanze e Figure</b> mostra automaticamente:</p>
  <ul>
    <li>Tutte le <b>distanze ciclometriche</b> (Δ) tra i numeri oro evidenziati</li>
    <li>Le <b>figure geometriche</b> formate: Opposti 180°, Triangolo 120°, Terzina 60°, Quadrato 90°</li>
  </ul>

  <div class="callout info">
    <b>💡 Consiglio:</b> usa il Cerchio come "lavagna visiva". Ogni numero che evidenzi diventa parte dell'analisi globale. Non esagerare: 3-8 numeri oro mantengono la lettura chiara.
  </div>
</section>

<!-- 5 -->
<section id="l38">
  <h2>5. Logica 38 — Colonne di Cadenza</h2>

  <h3>Concetto di base</h3>
  <p>Partendo da un <b>primo estratto</b> su una ruota, si deriva la sua <b>cadenza</b> (radice, complemento 90 o manuale). La <b>novina</b> (i 9 numeri con quella cadenza) si dispone in 3 colonne sul Cerchio: <b>ambo = interno+medio</b>, <b>escluso = esterno</b>. Il tasto <kbd>⇄</kbd> scambia l'escluso tra gli anelli.</p>

  <h3>Procedura</h3>
  <ol class="step-list">
    <li>Seleziona la <b>ruota</b> e inserisci il <b>primo estratto</b> (es. Genova 38).</li>
    <li>Scegli la <b>derivazione cadenza</b>: radice, complemento 90 o manuale.</li>
    <li>Imposta la <b>data di analisi</b> dal calendario (opzionale: se vuota usa l'ultima data).</li>
    <li>Clicca <kbd>🧭 Imposta cerchio & colonne</kbd>.</li>
    <li>Le 3 colonne appaiono sul Cerchio; clicca <kbd>⇄</kbd> per cambiare l'escluso.</li>
    <li>Clicca <kbd>💾 Salva (9 conc.)</kbd> per registrare la previsione nel tracker.</li>
  </ol>

  <h3>Shrink 9→2</h3>
  <p>Il tasto <kbd>📉 Shrink 9→2</kbd> applica il criterio <b>Rc ≥ Rx</b> (ritardo corrente ≥ massimo ritardo storico) per eliminare progressivamente i numeri meno "ritardati" fino a ottenere 2 numeri finali.</p>

  <div class="callout info">
    <b>📐 Semantica corretta:</b><br>
    • <b>Rc</b> = ritardo corrente dell'ambo nella combinazione<br>
    • <b>Rx</b> = massimo ritardo storico <b>chiuso</b> (gap tra due uscite, escluso il tratto iniziale). Corretto con <code>-1</code> per coincidere con Spaziometria.<br>
    • Il filtro <b>Rc ≥ Rx</b> segnala combinazioni "mai state così ritardate".
  </div>

  <h3>Terzine 280</h3>
  <p>Il tasto <kbd>🧩 Terzine 280</kbd> sviluppa la novina in 280 gruppi di 3 terzine e filtra con <b>Rc ≥ Rx(attesa empirica)</b>. Per i gruppi di terzine si usa l'<b>attesa empirica</b> (media gap), non il massimo ritardo: altrimenti il filtro sarebbe matematicamente quasi sempre falso.</p>

  <h3>Tracker per ruota</h3>
  <p>Il pannello sotto mostra <b>solo la ruota selezionata</b>, con hit sulle ultime 9 estrazioni reali. Ogni riga ha <kbd>✕</kbd> per eliminarla e <kbd>🗑️ Tutte</kbd> per cancellarle in blocco.</p>

  <div class="callout warn">
    <b>⚠️ Attenzione:</b> lo Shrink e le 280 terzine usano l'archivio <b>fino alla data scelta</b>. Il tracker verifica invece sempre sulle <b>ultime 9 estrazioni reali</b>, indipendentemente dalla data.
  </div>
</section>

<!-- 6 -->
<section id="distanze">
  <h2>6. Doppie Distanze v2.10.1</h2>

  <h3>Concetto matematico</h3>
  <p>Le uniche terzine con una distanza ripetuta 2 volte (doppia distanza Δ) sono le <b>terzine simmetriche in progressione</b>: <code>{x, x+Δ, x+2Δ} (mod 90)</code>. Il modulo le genera esaustivamente per ogni Δ trovata nell'estrazione sorgente.</p>

  <h3>Procedura completa</h3>
  <ol class="step-list">
    <li>Seleziona la <b>ruota sorgente</b> (dove è avvenuta l'estrazione di riferimento).</li>
    <li>Seleziona la <b>ruota target</b> (dove cercare le terzine) oppure <b>🌐 Tutte le ruote</b>.</li>
    <li>Inserisci i <b>numeri dell'estrazione sorgente</b> (es. <code>26 83 40 69 53</code>), separati da spazio, virgola o punto e virgola.</li>
    <li>Clicca <kbd>🔍 Cerca Terzine Ottimali</kbd>.</li>
    <li>Il sistema calcola le distanze ciclometriche, evidenzia in rosso le <b>doppie distanze</b> e genera le terzine simmetriche candidate.</li>
    <li>Applica il filtro <b>Rc ≥ Rx(attesa)</b> su ogni ruota target.</li>
  </ol>

  <h3>Output</h3>
  <ul>
    <li>Elenco delle <b>distanze ciclometriche</b> dell'estrazione sorgente (doppie in rosso)</li>
    <li>Lista delle <b>terzine simmetriche</b> trovate, ordinate per rapporto Rc/Rx</li>
    <li>Per ogni terzina: <b>Rc</b> (ritardo corrente), <b>Rx att.</b> (attesa empirica), <b>Rx max</b> (massimo storico chiuso)</li>
  </ul>

  <h3>Workflow integrato</h3>
  <div class="workflow">
    <h4> Inserisci nel Cerchio</h4>
    <p>Raccoglie i numeri delle terzine trovate (max 12) e li evidenzia in oro sul Cerchio. Da qui puoi lanciare il Motore Previsivo, la Piramide, le Spie, ecc.</p>
  </div>
  <div class="workflow">
    <h4>🧩 Sviluppa a gruppi di terzine + monitor Rc≥Rx</h4>
    <p>Partendo dai numeri trovati, genera tutte le terzine possibili (max 220) e le monitora con filtro Rc≥Rx(attesa) per ogni ruota target. Click su una riga = terzina evidenziata sul Cerchio.</p>
  </div>

  <h3>Correzione Rx (v2.10.1)</h3>
  <p>Il calcolo del gap chiuso è <code>g = i - lastHit - 1</code>: il concorso <code>i</code> è quello di uscita, quindi i concorsi di assenza sono <code>i - lastHit - 1</code>. Questo allinea Rx a Spaziometria (es. 106 invece di 107).</p>

  <div class="callout ok">
    <b>✅ Verifica:</b> sorgente <code>26 83 40 69 53</code> su Cagliari → doppie Δ evidenziate in rosso → target "Tutte le ruote" → elenco per ruota con Rc/Rx → click riga = terzina sul Cerchio → <kbd>🎯 Inserisci nel Cerchio</kbd> porta i numeri trovati sulle ghiere.
  </div>
</section>

<!-- 7 -->
<section id="motore">
  <h2>7. Motore Previsivo</h2>

  <h3>Funzionamento</h3>
  <p>Partendo dai numeri evidenziati (oro o verdi), il Motore calcola:</p>
  <ul>
    <li><b>Firma geometrica</b>: distanze ciclometriche tra tutti i numeri, ordinate per frequenza</li>
    <li><b>Figure classiche</b>: Opposti, Triangoli, Terzine, Quadrati</li>
    <li><b>Figure dinamiche</b>: vertici delle distanze dominanti</li>
    <li><b>Candidati</b>: numeri derivati da opposti, trigoni, sesti, somme cifre, distanze, somme, equidistanti</li>
  </ul>

  <h3>Output</h3>
  <ul>
    <li><b>Firma</b>: clicca una Δ per vedere SOLO quella figura (riclic = nascondi); <kbd>👁 Tutte</kbd> le mostra tutte</li>
    <li><b>Previsioni</b>: Amata, Ambo, Terno, Cinquina</li>
    <li><b>Lista candidati</b>: ordinata per score, con motivi</li>
  </ul>

  <h3>Pulsanti aggiuntivi</h3>
  <ul>
    <li><kbd> Calore</kbd>: mappa termica sui numeri più promettenti</li>
    <li><kbd>🎬 Tour</kbd>: slideshow automatico delle figure</li>
    <li><kbd>🧹</kbd>: ripulisce il Motore</li>
  </ul>

  <div class="callout info">
    <b>💡 Consiglio:</b> usa 3-5 numeri oro ben scelti (da un'estrazione recente o da una figura geometrica). Troppi numeri diluiscono la firma; troppo pochi non generano candidati significativi.
  </div>
</section>

<!-- 8 -->
<section id="convergenze">
  <h2>8. Convergenze</h2>

  <h3>Concetto</h3>
  <p>Analizza più estrazioni (manuali o da archivio) e trova i <b>numeri candidati</b> che compaiono in più estrazioni, con score cumulativo.</p>

  <h3>Caricamento dati</h3>
  <ul>
    <li><b>Da archivio</b>: seleziona ruota (o "Tutte") e numero estrazioni, poi <kbd>📥 Carica estrazioni nelle Convergenze</kbd></li>
    <li><b>Manuale</b>: incolla estrazioni nel formato <code>Bari 7 23 45 68 81</code>, una per riga, poi <kbd> Carica</kbd></li>
    <li><b>Demo</b>: <kbd>Demo</kbd> carica 5 estrazioni di esempio</li>
  </ul>

  <h3>Esecuzione</h3>
  <ol class="step-list">
    <li>Filtra per ruota (o "Tutte le ruote").</li>
    <li>Clicca <kbd>🔀 Convergenze</kbd>.</li>
    <li>I numeri ordinati per frequenza e score appaiono; click per evidenziarli.</li>
    <li><kbd>🎯 Evidenzia i convergenti</kbd> porta i top 8 sul Cerchio.</li>
  </ol>

  <div class="callout info">
    <b>💡 Consiglio:</b> le convergenze sono più significative su 5-15 estrazioni della stessa ruota. Su troppe estrazioni il rumore aumenta.
  </div>
</section>

<!-- 9 -->
<section id="backtest">
  <h2>9. Verifica a Ritroso (Backtest)</h2>

  <h3>Concetto</h3>
  <p>Simula una previsione al passato: scegli una data, il sistema usa le N estrazioni precedenti come base e confronta i candidati con l'estrazione reale successiva.</p>

  <h3>Procedura</h3>
  <ol class="step-list">
    <li>Seleziona <b>ruota</b> e <b>data</b> dal calendario.</li>
    <li>Imposta <b>estrazioni base</b> (default 60) e <b>top candidati</b> (default 10).</li>
    <li>Clicca <kbd>🔍 Verifica a ritroso</kbd>.</li>
    <li>Il sistema mostra: estrazione esatta del giorno (se esiste), top candidati, estrazione reale successiva, match.</li>
  </ol>

  <h3>Score del candidato</h3>
  <p>Combinazione di: <b>ritardo</b> + <b>bonus figure</b> (opposto +15, trigono +8, sesto +5) + <b>frequenza</b>.</p>

  <div class="callout warn">
    <b>⚠️ Attenzione:</b> il backtest è uno strumento di <b>validazione metodologica</b>, non di previsione. Usalo per testare se un metodo avrebbe funzionato nel passato, non per scegliere numeri futuri.
  </div>
</section>

<!-- 10 -->
<section id="manna">
  <h2>10. Metodo delle 3 Estrazioni (Manna)</h2>

  <h3>Concetto</h3>
  <p>Partendo dalle ultime 3 estrazioni di una ruota, calcola:</p>
  <ul>
    <li><b>Somme verticali</b> (E1+E2+E3) fuori 90</li>
    <li><b>Differenze</b> E1-E2 e E2-E3 fuori 90</li>
    <li><b>Spie incrociate</b> E1+E3 fuori 90</li>
  </ul>

  <h3>Procedura</h3>
  <ol class="step-list">
    <li>Seleziona la <b>ruota</b>.</li>
    <li>Clicca <kbd>Calcola</kbd>.</li>
    <li>Le 3 estrazioni e i calcoli appaiono in griglia.</li>
    <li><kbd>🎯 Evidenzia previsioni</kbd> porta i numeri (max 12) sul Cerchio.</li>
  </ol>

  <div class="callout info">
    <b>💡 Consiglio:</b> il Manna è un metodo "a caldo" sulle ultime estrazioni. Usalo in combinazione con altri moduli per conferme incrociate.
  </div>
</section>

<!-- 11 -->
<section id="piramide">
  <h2>11. Piramide Ciclotimica</h2>

  <h3>Concetto</h3>
  <p>Costruisce la piramide dalle <b>differenze successive</b> dei numeri evidenziati (oro o verdi), fino al vertice finale.</p>

  <h3>Procedura</h3>
  <ol class="step-list">
    <li>Evidenzia 2-5 numeri sul Cerchio.</li>
    <li>Clicca <kbd>🔺 Genera Piramide</kbd>.</li>
    <li>La piramide appare con il <b>vertice</b> evidenziato in viola.</li>
    <li><kbd>🎯 Evidenzia vertice</kbd> lo porta sul Cerchio.</li>
  </ol>

  <div class="callout info">
    <b>💡 Consiglio:</b> la piramide è più significativa con 4-5 numeri ben scelti (da un'estrazione o da una figura). Con 2 numeri il vertice è banale.
  </div>
</section>

<!-- 12 -->
<section id="spie">
  <h2>12. Spie per Ruota</h2>

  <h3>Concetto</h3>
  <p>Analizza i numeri evidenziati e mostra quali numeri <b>"annunciano" storicamente</b> nelle 5 estrazioni successive alla loro uscita.</p>

  <h3>Procedura</h3>
  <ol class="step-list">
    <li>Evidenzia almeno un numero sul Cerchio.</li>
    <li>Seleziona la <b>ruota</b>.</li>
    <li>Clicca <kbd>Analizza Spie</kbd>.</li>
    <li>Per ogni spia: numero di uscite storiche e top 3 numeri annunciati con percentuale.</li>
    <li>Click su un numero spia per evidenziarlo.</li>
  </ol>

  <div class="callout warn">
    <b>⚠️ Attenzione:</b> le spie sono statistiche storiche, non garanzie. Un numero può "annunciare" un altro nel 30% dei casi: significa che nel 70% non lo fa.
  </div>
</section>

<!-- 13 -->
<section id="figure">
  <h2>13. Figure Mancanti (Ritardo Figure)</h2>

  <h3>Concetto</h3>
  <p>Mostra quali <b>figure geometriche</b> (Opposti 180°, Triangolo 120°, Terzina 60°, Quadrato 90°) non escono da più tempo sulla ruota scelta, analizzando le ultime 100 estrazioni.</p>

  <h3>Procedura</h3>
  <ol class="step-list">
    <li>Seleziona la <b>ruota</b>.</li>
    <li>Clicca <kbd>Analizza</kbd>.</li>
    <li>Le figure sono ordinate per ritardo; quelle con ritardo >15 sono evidenziate in rosso.</li>
  </ol>

  <div class="callout info">
    <b>💡 Consiglio:</b> le figure con alto ritardo sono candidate per "ritorno", ma non c'è garanzia statistica. Usale come spunto, non come certezza.
  </div>
</section>

<!-- 14 -->
<section id="cadenze">
  <h2>14. Cadenze & Decine</h2>

  <h3>Concetto</h3>
  <p>Analizza la <b>distribuzione</b> dei numeri evidenziati per <b>cadenza</b> (cifra finale) e <b>decina</b>.</p>

  <h3>Output</h3>
  <ul>
    <li><b>Griglia cadenze 0-9</b>: fredde (assenti, bordo rosso) e calde (≥2 numeri, bordo verde)</li>
    <li><b>Griglia decine 1-10</b>: stessa codifica</li>
  </ul>

  <div class="callout info">
    <b>💡 Consiglio:</b> usa questa analisi per bilanciare la scelta dei numeri: evita di concentrarti su una sola cadenza o decina.
  </div>
</section>

<!-- 15 -->
<section id="allineamenti">
  <h2>15. Allineamenti</h2>

  <h3>Concetto</h3>
  <p>Cerca le <b>migliori configurazioni</b> dei 3 anelli per massimizzare i numeri evidenziati sui 4 punti cardinali (N, S, E, O).</p>

  <h3>Modalità</h3>
  <ul>
    <li><b>🟡 Oro + 🟢 Verde</b>: considera entrambi i tipi</li>
    <li><b>🟡 Solo numeri evidenziati</b>: solo oro</li>
    <li><b>🟢 Solo estrazione caricata</b>: solo verdi</li>
  </ul>

  <h3>Procedura</h3>
  <ol class="step-list">
    <li>Evidenzia numeri o carica un'estrazione.</li>
    <li>Seleziona la modalità.</li>
    <li>Clicca <kbd>🔍 Trova migliori configurazioni</kbd>.</li>
    <li>Scegli una delle top 5 configurazioni e clicca <kbd>Applica</kbd>.</li>
    <li>Il dettaglio mostra i numeri sui cardinali.</li>
  </ol>

  <div class="callout warn">
    <b>⚠️ Attenzione:</b> la ricerca esplora 32³ = 32.768 combinazioni. Su dispositivi lenti può richiedere 1-2 secondi.
  </div>
</section>

<!-- 16 -->
<section id="promettenti">
  <h2>16. Numeri Promettenti</h2>

  <h3>Concetto</h3>
  <p><b>Aggrega</b> i risultati di Motore, Convergenze, Backtest, Manna e Piramide con <b>regola del Fuori 90</b> applicata a tutti i calcoli.</p>

  <h3>Score</h3>
  <p>Ogni numero riceve un <b>score ponderato</b> per fonte, moltiplicato per un bonus se compare in più fonti. Il top assoluto è evidenziato in <b>arancione</b>.</p>

  <h3>Legenda fonti</h3>
  <ul>
    <li>🟡 <b>Motore</b></li>
    <li>🔵 <b>Convergenze</b></li>
    <li>🟢 <b>Backtest</b></li>
    <li>🔴 <b>Manna</b></li>
    <li> <b>Piramide</b></li>
  </ul>

  <h3>Procedura</h3>
  <ol class="step-list">
    <li>Esegui almeno un'analisi (Motore, Convergenze, Backtest, Manna o Piramide).</li>
    <li>Clicca <kbd>↻ Aggiorna</kbd> nel pannello Numeri Promettenti.</li>
    <li>I top 12 numeri appaiono con score e fonti; click per evidenziarli sul Cerchio.</li>
  </ol>

  <div class="callout ok">
    <b>✅ Consiglio d'oro:</b> i numeri che compaiono in <b>3+ fonti</b> sono i più interessanti. Usa questo pannello come "filtro finale" prima di scegliere.
  </div>
</section>

<!-- 17 -->
<section id="statistiche">
  <h2>17. Statistiche & Pattern</h2>

  <h3>Top 10 Frequenti</h3>
  <p>Analizza le ultime 100 estrazioni della ruota e mostra i 10 numeri più frequenti, con ritardo dall'ultima uscita. Click per evidenziare.</p>

  <h3>Pattern Coppie e Triple</h3>
  <p>Analizza le ultime 200 estrazioni e mostra le 15 coppie e 10 triple più frequenti. Click per evidenziare i numeri.</p>

  <div class="callout info">
    <b>💡 Consiglio:</b> usa le statistiche per <b>confermare</b> scelte fatte con altri metodi, non per scegliere direttamente. I numeri frequenti non sono "più probabili" nel futuro.
  </div>
</section>

<!-- 18 -->
<section id="periodo">
  <h2>18. Verifica Estrazioni per Periodo</h2>

  <h3>Concetto</h3>
  <p>Conta quante estrazioni ci sono state su una ruota in un periodo specifico.</p>

  <h3>Procedura</h3>
  <ol class="step-list">
    <li>Seleziona la <b>ruota</b> (usa quella della Logica 38).</li>
    <li>Imposta <b>Dal</b> e <b>Al</b> dal calendario.</li>
    <li>Clicca <kbd>Conta Estrazioni</kbd>.</li>
    <li>Il risultato mostra il conteggio, prima e ultima data.</li>
  </ol>

  <div class="callout info">
    <b>💡 Utilizzo:</b> utile per verificare quanti concorsi copre un'analisi Logica 38 o per calcolare la durata di un ritardo storico.
  </div>
</section>

<!-- 19 -->
<section id="config">
  <h2>19. Configurazioni salvabili</h2>

  <h3>Concetto</h3>
  <p>Salva la posizione degli anelli e i numeri evidenziati per richiamarli in seguito.</p>

  <h3>Procedura</h3>
  <ul>
    <li><b>Salva</b>: inserisci un nome e clicca <kbd>Salva</kbd></li>
    <li><b>Carica</b>: clicca il nome nella lista</li>
    <li><b>Elimina</b>: clicca <kbd>✕</kbd> accanto al nome</li>
  </ul>

  <h3>Condivisione</h3>
  <p>Il tasto <kbd>🔗 Condividi</kbd> genera un link con offset e numeri evidenziati, copiabile negli appunti.</p>
</section>

<!-- 20 -->
<section id="workflow">
  <h2>20. Workflow operativi consigliati</h2>

  <h3> Workflow 1: Analisi classica post-estrazione</h3>
  <div class="workflow">
    <ol>
      <li>Carica l'ultima estrazione della ruota target (<kbd> Ultima estrazione</kbd>).</li>
      <li>Analizza <b>Cadenze & Decine</b> per bilanciare la scelta.</li>
      <li>Lancia il <b>Motore Previsivo</b> (<kbd>⚡</kbd>) e studia la firma.</li>
      <li>Verifica le <b>Spie</b> dei numeri estratti.</li>
      <li>Controlla le <b>Figure Mancanti</b> per ritorni probabili.</li>
      <li>Aggiorna i <b>Numeri Promettenti</b> (<kbd>↻</kbd>).</li>
      <li>Scegli 5-8 numeri dai top promettenti.</li>
    </ol>
  </div>

  <h3>🧭 Workflow 2: Logica 38 con Shrink</h3>
  <div class="workflow">
    <ol>
      <li>Identifica il primo estratto di una ruota (es. da un'estrazione recente).</li>
      <li>Imposta <b>Logica 38</b> con ruota, numero e data.</li>
      <li>Esplora le 3 colonne con <kbd>⇄</kbd> per trovare la combinazione più interessante.</li>
      <li>Lancia <kbd>📉 Shrink 9→2</kbd> per ridurre a 2 numeri.</li>
      <li>Se vuoi più numeri, usa <kbd>🧩 Terzine 280</kbd> e filtra Rc≥Rx.</li>
      <li>Salva la previsione con <kbd></kbd> per il tracker.</li>
    </ol>
  </div>

  <h3>🔍 Workflow 3: Doppie Distanze</h3>
  <div class="workflow">
    <ol>
      <li>Prendi un'estrazione recente con numeri "interessanti" (es. con figure geometriche).</li>
      <li>Inseriscila nel modulo <b>Doppie Distanze</b> come sorgente.</li>
      <li>Seleziona target "🌐 Tutte le ruote" per massima copertura.</li>
      <li>Clicca <kbd>🔍 Cerca Terzine Ottimali</kbd>.</li>
      <li>Studia le terzine con Rc/Rx più alto.</li>
      <li>Clicca <kbd>🎯 Inserisci nel Cerchio</kbd> per portare i numeri sul Cerchio.</li>
      <li>Lancia il Motore o la Piramide sui numeri trovati.</li>
      <li>Oppure clicca <kbd>🧩 Sviluppa…</kbd> per il monitor terzine per ruota.</li>
    </ol>
  </div>

  <h3>🔀 Workflow 4: Convergenze multi-ruota</h3>
  <div class="workflow">
    <ol>
      <li>Carica 5-10 estrazioni recenti di più ruote (<kbd>📥 Carica ruote dall'archivio</kbd>).</li>
      <li>Filtra "Tutte le ruote" e lancia <kbd>🔀 Convergenze</kbd>.</li>
      <li>I numeri forti (presenti in più estrazioni) appaiono ordinati.</li>
      <li>Evidenzia i top 5-8 e lancia il Motore per approfondimento.</li>
    </ol>
  </div>

  <h3>🕰️ Workflow 5: Backtest di validazione</h3>
  <div class="workflow">
    <ol>
      <li>Scegli una data passata (es. 1 mese fa).</li>
      <li>Imposta 60 estrazioni base e 10 candidati.</li>
      <li>Lancia il Backtest e studia il match con l'estrazione reale.</li>
      <li>Ripeti su 5-10 date diverse per valutare la consistenza del metodo.</li>
    </ol>
  </div>
</section>

<!-- 21 -->
<section id="avvertenze">
  <h2>21. Avvertenze, limiti e buona pratica</h2>

  <h3>⚠️ Avvertenze fondamentali</h3>
  <ul>
    <li><b>Il Lotto è casuale.</b> Nessun metodo, software o sistema può prevedere i numeri estratti.</li>
    <li><b>Gioca responsabilmente.</b> Stabilisci un limite di spesa mensile e non superarlo mai.</li>
    <li><b>Non inseguire le perdite.</b> Se perdi, non aumentare la posta per "recuperare".</li>
    <li><b>Questo strumento è per studio.</b> Usalo per esplorare metodi tradizionali, non per giocare soldi che non puoi permetterti di perdere.</li>
  </ul>

  <h3> Limiti tecnici</h3>
  <ul>
    <li><b>Archivio:</b> il caricamento richiede 10-30 secondi. Non chiudere il browser durante il processo.</li>
    <li><b>Cache:</b> resta nel browser. Se cambi PC o cancelli i dati del browser, devi ricaricare l'archivio.</li>
    <li><b>Allineamenti:</b> la ricerca esplora 32.768 combinazioni; su dispositivi lenti può richiedere 1-2 secondi.</li>
    <li><b>Doppie Distanze:</b> con "Tutte le ruote" e archivio completo, la ricerca può richiedere 1-2 secondi.</li>
    <li><b>Sviluppo terzine:</b> limitato a 12 numeri (220 terzine) per restare istantaneo.</li>
  </ul>

  <h3>✅ Buona pratica</h3>
  <ul>
    <li><b>Mantieni il Cerchio pulito.</b> 3-8 numeri oro sono ideali; troppi numeri diluiscono l'analisi.</li>
    <li><b>Usa più moduli.</b> La convergenza di risultati da metodi diversi è più significativa di un singolo metodo.</li>
    <li><b>Documenta le tue analisi.</b> Usa <kbd> Configurazioni</kbd> per salvare setup interessanti.</li>
    <li><b>Verifica con backtest.</b> Prima di fidarti di un metodo, testalo su date passate.</li>
    <li><b>Non ossessionarti.</b> Il Lotto è intrattenimento, non un lavoro o un investimento.</li>
  </ul>

  <div class="callout err">
    <b>🚫 Non fare mai:</b> giocare soldi che ti servono per vivere, inseguire perdite, credere che un metodo "funcioni sempre", prestare soldi per giocare, giocare sotto l'effetto di alcol o stress.
  </div>
</section>

<!-- 22 -->
<section id="glossario">
  <h2>22. Glossario</h2>
  <dl class="glossary">
    <dt>Rc (Ritardo corrente)</dt>
    <dd>Numero di concorsi dall'ultima uscita dell'ambo/terzina nella combinazione.</dd>

    <dt>Rx (Ritardo massimo storico)</dt>
    <dd>Più lungo intervallo di concorsi senza uscite dell'ambo/terzina, calcolato sui gap chiusi (escluso il tratto iniziale). Corretto con <code>-1</code> per allinearsi a Spaziometria.</dd>

    <dt>Rx att. (Attesa empirica)</dt>
    <dd>Media dei gap storici. Usata per i gruppi di terzine (altrimenti il filtro Rc≥Rx sarebbe quasi sempre falso).</dd>

    <dt>Filtro Rc ≥ Rx</dt>
    <dd>Segnala combinazioni "mai state così ritardate" rispetto al loro storico.</dd>

    <dt>Novina</dt>
    <dd>I 9 numeri con la stessa cadenza (cifra finale) di un numero dato.</dd>

    <dt>Cadenza</dt>
    <dd>Cifra finale di un numero (0-9). Derivabile da radice (somma cifre ridotta), complemento 90, o manuale.</dd>

    <dt>Distanza ciclometrica (Δ)</dt>
    <dd>Distanza minima tra due numeri sul ciclo di 90: <code>min(|a-b|, 90-|a-b|)</code>.</dd>

    <dt>Doppia distanza</dt>
    <dd>Distanza che compare almeno 2 volte tra i numeri di un'estrazione. Genera terzine simmetriche <code>{x, x+Δ, x+2Δ}</code>.</dd>

    <dt>Terzina simmetrica</dt>
    <dd>Terzina in progressione aritmetica mod 90: <code>{x, x+Δ, x+2Δ}</code>. Unica forma di terzina con doppia distanza.</dd>

    <dt>Fuori 90</dt>
    <dd>Regola di riduzione: se un calcolo supera 90, si sottrae 90 fino a rientrare in 1-90.</dd>

    <dt>Firma geometrica</dt>
    <dd>Distribuzione delle distanze ciclometriche tra i numeri evidenziati, ordinata per frequenza.</dd>

    <dt>Convergenza</dt>
    <dd>Numero candidato che compare in più estrazioni analizzate, con score cumulativo.</dd>

    <dt>Backtest</dt>
    <dd>Simulazione di una previsione al passato per validare un metodo.</dd>

    <dt>Spia</dt>
    <dd>Numero che storicamente "annuncia" l'uscita di altri numeri nelle estrazioni successive.</dd>

    <dt>Shrink 9→2</dt>
    <dd>Procedura di riduzione della novina a 2 numeri tramite eliminazione progressiva basata su Rc≥Rx.</dd>

    <dt>Terzine 280</dt>
    <dd>Sviluppo della novina in 280 gruppi di 3 terzine, filtrati con Rc≥Rx(attesa).</dd>

    <dt>Epoca storica</dt>
    <dd>Filtro temporale sull'archivio: Completo, Venus (2005-oggi), 4 Estrazioni (2023-oggi).</dd>

    <dt>IndexedDB</dt>
    <dd>Database del browser usato per salvare l'archivio in modo persistente tra sessioni.</dd>

    <dt>Vertici interattivi</dt>
    <dd>Punti delle figure geometriche sul Cerchio; mouse/tap mostra il numero coperto ingrandito.</dd>
  </dl>
</section>

<div style="text-align:center;padding:30px 20px;color:#8a7a5c;font-size:13px;font-style:italic;">
  Cerchio di Spilimbergo Suite Completa v2.10 — Manuale operativo<br>
  Strumento di studio e intrattenimento. Gioca responsabilmente.
</div>

</div>

<a href="#" class="back-top" title="Torna su">↑</a>

</body>
</html>
 

Ultima estrazione Lotto

  • Estrazione del lotto
    sabato 05 settembre 2026
    Bari
    26
    83
    40
    69
    53
    Cagliari
    13
    86
    41
    22
    23
    Firenze
    45
    44
    78
    80
    89
    Genova
    86
    69
    89
    28
    06
    Milano
    36
    04
    65
    51
    30
    Napoli
    73
    06
    80
    45
    18
    Palermo
    61
    78
    14
    17
    24
    Roma
    89
    29
    17
    32
    41
    Torino
    78
    20
    74
    71
    83
    Venezia
    19
    85
    16
    39
    42
    Nazionale
    11
    60
    23
    17
    07
    Estrazione Simbolotto
    Palermo
    30
    35
    22
    02
    14
Indietro
Alto