Novità

Genera Covering Designs Ridotti

lottopython, hai messo uno strumento veramente da studiare. quasi mi intriga di ritornare a riprendere alcune cose, tipo gli aquiloni, in alcuni casi ne ho visto qualcuno apparire. quelli funzionavano bene in quei tempi passati.
 
Ultima modifica:
lottophyton, hai messo uno strumento veramente da studiare. quasi mi intria di ritornare a riprendere alcune cose, tipo gli aquiloni, in alcuni casi ne ho visto qualcuno apparire. quelli funzionavano bene in quei tempi passati.
si rubino è uno strumento valido al quale decisamente è possibile implementare e/o migliorare logiche che tendono a migliorare la statistica e che lavorano in sinergia con il cerchio , ci sono aspetti interessanti anche dal punto di vista grafico per una migliore interpretazione . Mi rendo conto che non è molto intuitivo il menu a tempo libero vedro' il da farsi .
 
lo sto provando, ho 1 domande da farti, è un esempio tratta dalla foto casuale

1) è possibile mettere su questo triangolo che lo sfondo violetta indica, mettere le distanze tra i 3 numeri?
o sono rilevabili dal vertice o distanza 42 (62-20) (26 68) (74 26), ho capito giusto, in tal caso non serve variare niente perchè è il vertice o distanza??


l'ambata è il 23 perchè è il diametrale del 68?
 

Allegati

  • Screenshot 2026-09-08 140537.png
    Screenshot 2026-09-08 140537.png
    279 KB · Visite: 4
  • Screenshot 2026-09-08 141010.png
    Screenshot 2026-09-08 141010.png
    41,1 KB · Visite: 4
Ultima modifica:
cosi dovrebbe andare ho aggiunto la dist cicl fissa, NB cliccando sui pallini che nascondo i numeri puoi renderli visibili e ingrandirli

Figura Δ42 ×2" indica solo la distanza dominante ripetuta 2 volte, non le distanze di tutti i lati. I lati del triangolo (vertici 20‑26‑68) sono in realtà:Lato
Distanza ciclometrica
20 – 68
Δ42 ✓
26 – 68
Δ42 ✓
20 – 26

Δ6 (è il badge "Δ6 ×1" )

Quindi si , ma con una piccola nota: le coppie (62‑20) e (74‑26) non sono lati del triangolo: 62 e 74 sono gli sviluppi (20+42=62 e 26+48=74, cioè Δ e 90−Δ), ovvero i segmenti vertice→sviluppo che leggi nella riga "Sviluppi (Δ e 90−Δ): 62, 74". L'unico lato corretto che hai citato è (26‑68); l'altro lato Δ42 è (20‑68).

y50877460991.png

L'ambata 23: sì, è il diametrale del 68 (ma non solo)
il pannello lo mostra nei motivi: "opposto di 68 • equidistante di 20‑26 → 4.5".

Diametrale/opposto ciclometrico di 68: 68 + 45 = 113 → fuori 90 → 23 (peso 3.0). È lo stesso criterio della figura "Opposti 180°" (rot(n,45)).
In più coincide anche con l'equidistante di 20 e 26: (20+26)/2 = 23 (peso 1.5).

Somma = 4.5, il punteggio più alto, quindi finisce in testa come Amata. Gli inseguitori a 3.0 (62, 65, 71, 74) hanno una sola motivazione ciascuno (opposti di 20/26 o sviluppi Δ42), ed è per questo che il 23 domina.

il "diametrale" qui è quello ciclometrico sul ciclo 1‑90 (+45), che è poi quello che il motore disegna come linea Opposti sul cerchio — non il diametro visivo dei 32 settori.

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

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

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

    * { box-sizing: border-box; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    const ringGroups = [];

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

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

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

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

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

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

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

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

    let dragging = null;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // --- AGGIUNGI questa funzione (es. subito prima di drawFigDinamica) ---
    function drawEdgeDistances(pts, color) {
      for (let i = 0; i < pts.length; i++)
        for (let j = i + 1; j < pts.length; j++) {
          const a = pts[i], b = pts[j];
          const d = distanzaCiclo(a.num, b.num);
          const mx = (a.x + b.x) / 2, my = (a.y + b.y) / 2;
          const dx = mx - CX, dy = my - CY, len = Math.hypot(dx, dy) || 1;
          addLabel(mx + dx / len * 16, my + dy / len * 16, 'Δ' + d, color);
        }
    }
    
    // ============================================================
    // FIGURA DINAMICA (v2.5)
    // ============================================================
    function drawFigDinamica(pts, color, label) {
      overlayG.appendChild(el('path', { d: polygonPath(pts), class: 'fig-shape fig-pulse', fill: color, stroke: color, 'stroke-dasharray': '6 4' }));
      drawRays(pts, color);
      pts.forEach(p => drawVertex(p, color, 5, p.num));
      const c = midPoint(pts);
      overlayG.appendChild(el('circle', { cx: c.x, cy: c.y, r: 4, class: 'fig-center', fill: color, stroke: '#fff', 'stroke-width': 1.5 }));
      addLabel(c.x, c.y - 16, label, color);
      // v2.10.2: distanza ciclometrica su ogni lato (ridisegnata a ogni renderRing)
      for (let i = 0; i < pts.length; i++) {
        for (let j = i + 1; j < pts.length; j++) {
          const a = pts[i], b = pts[j];
          const d = distanzaCiclo(a.num, b.num);
          const mx = (a.x + b.x) / 2, my = (a.y + b.y) / 2;
          const dx = mx - CX, dy = my - CY, len = Math.hypot(dx, dy) || 1;
          addLabel(mx + dx / len * 16, my + dy / len * 16, 'Δ' + d, color);
        }
      }
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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