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: 5
  • Screenshot 2026-09-05 172238.png
    Screenshot 2026-09-05 172238.png
    308,2 KB · Visite: 7
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: 6
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>
 

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