<?php
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: 0');
?>
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
<title>Roleta de teste Wander</title>
<style>
  :root{
    --bg-image: url('../uploads/roulette_73/fundo roleta_68ea8935858d53.15854575.jpg');
    --bg-image-mobile: var(--bg-image); /* Fallback para o background normal */
    --overlay: none;
    --txt:#eef3ff;
    --wheel-size: 80vmin;

    /* Ponteiro: tamanho e "mordida" dentro da roda (padrão para todas as posições) */
    --pointer-size: clamp(40px, 5.2vw, 62px);
    --pointer-inset: clamp(26px, 3.2vw, 36px);
  }
    /* Modifique o media query existente para usar a variável: */
    @media (max-width: 768px) {
        body::before{
            background-image: var(--overlay), var(--bg-image-mobile) !important;
        }
    }
  *{ box-sizing:border-box }
  html,body{ height:100% }
  body{
    margin:0; color:var(--txt);
    font-family: ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;
    min-height:100svh; overflow:hidden;
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    padding: max(10px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right)) max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
    gap:14px;
  }
  body::before{
    content:""; position:fixed; inset:0; z-index:-2;
    background-image: var(--bg-image);
    background-size: cover, cover; background-position:center,center; background-repeat:no-repeat,no-repeat;
  }
  .title{
    text-align:center; font-weight:1000; letter-spacing:.18em; text-transform:uppercase;
    font-size:clamp(14px,2.6vw,30px); margin:0 0 4px 0;
    color:#ffffff;
    font-family: 'Poppins', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    text-shadow:0 2px 10px rgba(0,0,0,.6), 0 0 12px rgba(96,165,250,.45);
}
  .subtitle{
    margin-top:-4px; text-align:center; opacity:.85; font-weight:700;
    font-size:clamp(11px,1.8vw,16px); letter-spacing:.05em;
    color:#eef3ff;
    font-family: 'Poppins', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}

  .wheelbox{ position:relative; width:var(--wheel-size); height:var(--wheel-size);  cursor: pointer; }
  .wheelbox>.layer{ position:absolute; inset:0 }
  .layer canvas{ position:absolute; inset:0; width:100%; height:100%; display:block; user-select:none; -webkit-tap-highlight-color:transparent; touch-action:none }
  #fxBG{ z-index:1; pointer-events:none } #wheel{ z-index:2 } #fx{ z-index:3; pointer-events:none }

  /* ===== PONTEIRO BONITO (cor + posição) ===== */
  .pointer{
    position:absolute; z-index:5; pointer-events:none;
    width:var(--pointer-size); height:var(--pointer-size);

    /* vars preenchidas via JS */
    --pc:#A855F7;      /* base */
    --pc-hi:#C28CFF;   /* highlight */
    --pc-mid:#A855F7;  /* meio */
    --pc-lo:#6E2CB2;   /* sombra */
    --pc-rim:#4B1A86;  /* aro/borda */

    border:clamp(3px,.6vw,5px) solid var(--pc-rim);
    filter:drop-shadow(0 12px 16px rgba(0,0,0,.55));

    /* gradiente 3D estável pra qualquer cor */
    background:
      radial-gradient(120% 120% at 28% 20%, rgba(255,255,255,.55) 0 18%, rgba(255,255,255,0) 55%),
      linear-gradient(180deg, var(--pc-hi) 0%, var(--pc-mid) 55%, var(--pc-lo) 100%);
    box-shadow:
      0 0 0 1px rgba(255,255,255,.14) inset,
      0 -10px 16px rgba(0,0,0,.33) inset;
  }
  .pointer::after{
    content:""; position:absolute; inset:0; pointer-events:none;
    background: radial-gradient(110% 90% at 36% 10%, rgba(255,255,255,.55), rgba(255,255,255,0) 60%);
    mix-blend-mode:screen; opacity:.75;
  }
  .pointer.bump{ animation:glowPulse .18s ease }
  @keyframes glowPulse{ 50%{ filter: drop-shadow(0 12px 16px rgba(0,0,0,.55)) brightness(1.07); } }

  /* POSIÇÕES padronizadas (mesma "mordida" em todas) */
  .pointer.pos-right{
    top:50%;
    left: calc(100% - var(--pointer-inset));
    transform: translateY(-50%);
    clip-path: polygon(100% 0, 0 50%, 100% 100%);
    border-left: 0;
  }
  .pointer.pos-left{
    top:50%;
    left: calc(0px - (var(--pointer-size) - var(--pointer-inset)));
    transform: translateY(-50%);
    clip-path: polygon(0 0, 100% 50%, 0 100%);
    border-right: 0;
  }
  .pointer.pos-top{
    top: calc(0px - (var(--pointer-size) - var(--pointer-inset)));
    left: 50%;
    transform: translateX(-50%);
    clip-path: polygon(0 0, 50% 100%, 100% 0);
    border-bottom: 0;
  }
  .pointer.pos-bottom{
    top: calc(100% - var(--pointer-inset));
    left: 50%;
    transform: translateX(-50%);
    clip-path: polygon(0 100%, 50% 0, 100% 100%);
    border-top: 0;
  }

  /* modal */
  .modal{ position:fixed; inset:0; display:none; align-items:center; justify-content:center; z-index:50 }
  .modal.open{ display:flex }
  .modal .backdrop{ position:absolute; inset:0; background:rgba(3,6,23,.72) }
  .modal canvas#fxModal{ position:absolute; inset:0; width:100%; height:100%; z-index:1; pointer-events:none }
  .modal .card{ position:relative; z-index:2; width:min(92vw,520px); background:linear-gradient(180deg, rgba(17,24,39,.92), rgba(10,15,30,.92)); color:#e5e7eb; border:1px solid rgba(255,255,255,.14); border-radius:18px; box-shadow:0 20px 80px rgba(0,0,0,.6); padding:20px; display:grid; gap:12px }
  .modal .close{ position:absolute; top:8px; right:8px; width:45px; height:45px; border-radius:999px; border:1px solid rgba(255,255,255,.2); background:rgba(255,255,255,.08); cursor: pointer; font-size: 40px; color: #ffff; }
  .modal .bubble{ width:140px; height:140px; border-radius:999px; display:grid; place-items:center; background:#ef4444; box-shadow:0 10px 30px rgba(255,60,60,.35) inset; overflow:hidden; justify-self:center; text-align:center; padding:8px 10px }
  .modal .bubble .qty{ font:900 36px/1 system-ui; color:#fff }
  .modal .bubble .unit{ font:800 12px/1 system-ui; color:#fff; margin-top:4px }
  .modal .bubble #modalGameInBubble{ font:700 11px/1.15 system-ui; color:#fff; opacity:.95; margin-top:6px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; width:100% }

/* ===== COOLDOWN STYLES MELHORADO ===== */
.cooldown-container {
  position: fixed;
  bottom: 25px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  text-align: center;
  margin: 0;
  padding: 0;
  height: auto;
  pointer-events: none;
}

.cooldown-message {
  font-size: 12px;
  color: #cbd5e0;
  font-weight: 500;
  background: rgba(15, 23, 42, 0.85);
  padding: 6px 14px;
  border-radius: 16px;
  display: inline-block;
  backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
  transition: all 0.3s ease;
  opacity: 0;
  transform: translateY(20px) scale(0.95);
}

.cooldown-message.show {
  opacity: 1;
  transform: translateY(0) scale(1);
}

.cooldown-message.warning {
  color: #feb2b2;
  background: rgba(254, 178, 178, 0.12);
  border-color: rgba(254, 178, 178, 0.2);
}

/* Efeito de pulso suave para atenção */
@keyframes gentlePulse {
  0%, 100% { transform: translateY(0) scale(1); }
  50% { transform: translateY(-2px) scale(1.02); }
}

.cooldown-message.warning {
  animation: gentlePulse 0.5s ease;
}

/* Para não afetar o layout da roleta */
.wheelbox.disabled {
  cursor: not-allowed !important;
  pointer-events: none !important;
  /* Nenhuma mudança visual na roleta */
} 

/* ===== MODAL DE LIMITE EXCEDIDO ===== */
.limit-modal {
  position: fixed;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

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

.limit-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(3, 6, 23, 0.85);
  backdrop-filter: blur(8px);
}

.limit-card {
  position: relative;
  z-index: 2;
  width: min(90vw, 450px);
  background: linear-gradient(135deg, rgba(17, 24, 39, 0.95), rgba(10, 15, 30, 0.95));
  color: #e5e7eb;
  border: 2px solid rgba(239, 68, 68, 0.3);
  border-radius: 20px;
  box-shadow: 0 25px 100px rgba(239, 68, 68, 0.3);
  padding: 30px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}

.limit-icon {
  font-size: 4rem;
  margin-bottom: 10px;
}

.limit-title {
  font: 900 28px/1.2 system-ui;
  color: #ef4444;
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.limit-message {
  font-size: 16px;
  line-height: 1.5;
  color: #e5e7eb;
  margin: 0;
}

.limit-details {
  background: rgba(239, 68, 68, 0.1);
  padding: 15px;
  border-radius: 12px;
  border: 1px solid rgba(239, 68, 68, 0.2);
  width: 100%;
}

.limit-count {
  font: 700 18px/1 system-ui;
  color: #ef4444;
}

.limit-timer {
  font: 900 32px/1 'Courier New', monospace;
  color: #3b82f6;
  background: rgba(59, 130, 246, 0.1);
  padding: 15px;
  border-radius: 12px;
  border: 2px solid rgba(59, 130, 246, 0.3);
  min-width: 120px;
}

.limit-info {
  font-size: 14px;
  color: #9ca3af;
  margin: 0;
}

.limit-cta {
  cursor: pointer;
  appearance: none;
  border: 0;
  padding: 16px 32px;
  border-radius: 12px;
  font-weight: 900;
  background: linear-gradient(135deg, #3b82f6, #06b6d4);
  color: #fff;
  box-shadow: 0 14px 40px rgba(3, 105, 161, 0.45);
  width: 100%;
  font-size: 16px;
  transition: all 0.2s ease;
}

.limit-cta:hover {
  transform: translateY(-2px);
  box-shadow: 0 18px 50px rgba(3, 105, 161, 0.6);
}

.limit-cta:active {
  transform: translateY(0);
}

.modal .bubble .qty, 
.modal .bubble .unit, 
.modal .bubble #modalGameInBubble {
  transition: color 0.3s ease;
  text-shadow: 0 1px 2px rgba(0,0,0,0.3); /* Melhora legibilidade */
}

/* ===== MODAL DE LIMITE TOTAL EXCEDIDO ===== */
.total-limit-modal .limit-card {
  border-color: rgba(239, 68, 68, 0.5);
  box-shadow: 0 25px 100px rgba(239, 68, 68, 0.4);
}

.total-limit-modal .limit-title {
  color: #dc2626;
  font-size: 24px;
}

.total-limit-modal .limit-message {
  font-size: 15px;
  line-height: 1.4;
}

.total-limit-modal .limit-details {
  background: rgba(220, 38, 38, 0.1);
  border-color: rgba(220, 38, 38, 0.3);
}

.total-limit-modal .limit-count {
  color: #dc2626;
  font-size: 16px;
}

.total-limit-modal .permanent-block {
  font: 900 28px/1 'Courier New', monospace;
  color: #dc2626;
  background: rgba(220, 38, 38, 0.1);
  padding: 15px;
  border-radius: 12px;
  border: 2px solid rgba(220, 38, 38, 0.3);
  min-width: 120px;
}

.total-limit-modal .limit-info {
  color: #ef4444;
  font-weight: 600;
}
h1.title{ font-size: var(--title-size, 30px) !important; }.subtitle{ font-size: var(--subtitle-size, 16px) !important; }.wheelbox{ display:block; margin-left:auto; margin-right:auto; }
@media (max-width: 768px){h1.title{ font-size: calc(var(--title-size, 30px) * 0.80) !important; }.subtitle{ font-size: calc(var(--subtitle-size, 16px) * 0.40) !important; }}
</style>

            <style>
            @media (max-width: 768px) {
                body::before {
                    background-image: url('../uploads/roulette_73/fundo roleta_68ea89358621e2.10798493.jpg') !important;
                }
            }
            @media (max-width: 768px){h1.title{ font-size: calc(var(--title-size, 30px) * 0.80) !important; }.subtitle{ font-size: calc(var(--subtitle-size, 16px) * 0.40) !important; }}
</style>
        
<!-- Google Fonts (gerado) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700;800;900&amp;display=swap">
</head>
<body style="--overlay: none;">
  <h1 class="title" id="title">Roleta</h1>
  <div class="subtitle" id="subtitle">Voce pode rodar 3 giros gratis</div>

  <div class="wheelbox" id="wheelbox" aria-live="polite">
    <div class="layer"><canvas id="fxBG" aria-hidden="true"></canvas></div>
    <div class="layer">
      <div class="pointer pos-right" id="pointer" aria-hidden="true"></div>
      <canvas id="wheel" aria-label="Roleta"></canvas>
    </div>
    <div class="layer"><canvas id="fx" aria-hidden="true"></canvas></div>
  </div>

<div class="cooldown-container" id="cooldownContainer" style="display: none;">
  <div class="cooldown-message" id="cooldownMessage"></div>
</div>  

  <!-- áudios (preenchidos pelo generator) -->
  <audio id="bgm"  src=""   preload="none" playsinline></audio>
  <audio id="tick" src=""    preload="none" playsinline></audio>
  <audio id="inicio" src="" preload="none" playsinline></audio>
  <audio id="ganhou" src="" preload="none" playsinline></audio>
  <audio id="perdeu" src="" preload="none" playsinline></audio>

  <!-- modal de resultado normal -->
  <div class="modal" id="resultModal" aria-hidden="true">
    <div class="backdrop" id="modalBackdrop"></div>
    <canvas id="fxModal"></canvas>
    <div class="card" role="dialog" aria-labelledby="modalTitle">
      <button class="close" id="modalClose" type="button" aria-label="Fechar">×</button>

      <div class="bubble" id="modalBubble">
        <div class="qty" id="modalQtyBig" style="display:none">50</div>
        <div class="unit" id="modalUnit" style="display:none">GIROS</div>
        <div id="modalGameInBubble" style="display:none"></div>
      </div>

      <h2 id="modalH2" style="margin:0;text-align:center;font:900 20px/1.3 system-ui">
        <span id="modalTitle">Resultado</span>
      </h2>
      <div id="modalDetail" style="text-align:center;opacity:.85">—</div>
      <div id="modalGame" style="display:none; text-align:center; opacity:.9; font-weight:800;"></div>

      <button id="modalCTA" type="button" style="cursor:pointer;appearance:none;border:0;padding:12px 18px;border-radius:12px;font-weight:900;background:linear-gradient(180deg,#3b82f6,#06b6d4);color:#fff;box-shadow:0 14px 40px rgba(3,105,161,.45)">Fechar</button>
    </div>
  </div>

  <!-- Modal de Limite Excedido -->
  <div class="limit-modal" id="limitModal" aria-hidden="true">
    <div class="limit-backdrop" id="limitBackdrop"></div>
    <div class="limit-card" role="dialog" aria-labelledby="limitTitle">
      <div class="limit-icon">⏰</div>
      
      <h2 class="limit-title" id="limitTitle">Limite Atingido</h2>
      
      <div class="limit-message" id="limitMessage" style="display: none;">
        Você atingiu o limite máximo de giros configurado para esta roleta.
      </div>

      <div class="limit-details" style="display: none;">
        <div class="limit-count">
          <span id="limitCurrentCount">0</span> / <span id="limitMaxCount">0</span> giros por <span id="limitType">hora</span>
        </div>
      </div>

      <div class="limit-timer" id="limitTimer" style="display: none;">00:00</div>

      <div class="limit-info" style="display: none;">
        Você poderá girar novamente quando o timer chegar a zero
      </div>

      <button class="limit-cta" id="limitCTA" type="button">
        Entendido
      </button>
    </div>
  </div>

  <!-- Modal de Limite Total Excedido -->
  <div class="limit-modal total-limit-modal" id="totalLimitModal" aria-hidden="true">
    <div class="limit-backdrop" id="totalLimitBackdrop"></div>
    <div class="limit-card" role="dialog" aria-labelledby="totalLimitTitle">
      <div class="limit-icon">🚫</div>
      
      <h2 class="limit-title" id="totalLimitTitle">Limite Total Atingido</h2>
      
      <div class="limit-message" id="totalLimitMessage">
        Você atingiu o limite total de giros configurado para esta roleta.
      </div>

      <div class="limit-details">
        <div class="limit-count">
          <span id="totalLimitCurrentCount">0</span> / <span id="totalLimitMaxCount">0</span> giros <span id="totalLimitPeriod">totais</span>
        </div>
      </div>

      <div class="permanent-block" id="totalLimitTimer">BLOQUEADO</div>

      <div class="limit-info" id="totalLimitInfo">
        Seu acesso a esta roleta foi bloqueado devido ao limite total excedido.
      </div>

      <button class="limit-cta" id="totalLimitCTA" type="button">
        Entendido
      </button>
    </div>
  </div>

<script>
window.ROULETTE_ID = 73;

/* ===== PRIZES (gerado) ===== */
const PRIZES = [{"primary":"TENTE","unit":"NOVAMENTE","color":"#db0a1f","weight":20,"kind":"LOSE","type":"LOSE","game":"TENTE NOVAMENTE","url":"https://lider.bet.br","image":"","imageFit":"contain","imageOpacity":95,"text_color":"#ffffff","text_size":24,"unit_text_size":13,"hide_text_all":0},{"primary":"10","unit":"GIROS","color":"#2bcb15","weight":10,"kind":"WIN","type":"WIN","game":"Seu Jogo","url":"https://lider.bet.br","image":"","imageFit":"cover","imageOpacity":95,"text_color":"#ffffff","text_size":24,"unit_text_size":13,"hide_text_all":0},{"primary":"TENTE","unit":"NOVAMENTE","color":"#d40c0c","weight":1,"kind":"LOSE","type":"LOSE","game":"","url":"https://lider.bet.br","image":"","imageFit":"cover","imageOpacity":95,"text_color":"#ffffff","text_size":24,"unit_text_size":13,"hide_text_all":0},{"primary":"30","unit":"GIROS","color":"#87d55d","weight":2,"kind":"WIN","type":"WIN","game":"","url":"https://lider.bet.br","image":"","imageFit":"cover","imageOpacity":95,"text_color":"#ffffff","text_size":24,"unit_text_size":13,"hide_text_all":0},{"primary":"TENTE","unit":"NOVAMENTE","color":"#e10e0e","weight":5,"kind":"LOSE","type":"LOSE","game":"","url":"https://lider.bet.br","image":"","imageFit":"cover","imageOpacity":95,"text_color":"#ffffff","text_size":24,"unit_text_size":13,"hide_text_all":0},{"primary":"50","unit":"GIROS","color":"#29700f","weight":20,"kind":"WIN","type":"WIN","game":"","url":"https://lider.bet.br","image":"../uploads/roulette_73/tigre_sortudo_5_68ea8e5ad2b683.23362161.png","imageFit":"cover","imageOpacity":95,"text_color":"#ffffff","text_size":24,"unit_text_size":13,"hide_text_all":1}];
// DEBUG: Segment weights analysis
console.log('🎰 ROULETTE SEGMENT WEIGHTS:');
PRIZES.forEach((p, i) => {
  console.log(`Segment ${i}: Weight ${p.weight} -> Effective: ${p.weight <= 1 ? 0 : p.weight} - ${p.primary} ${p.unit} - HideText: ${p.hide_text_all}`);
});

/* ===== Botão central (o generator substitui) ===== */
const SPIN_TEXT = "GIRAR";
/* ===== FONTE (gerado) ===== */
const FONT_FAMILY = "Poppins";
const FONT_STACK  = "'" + FONT_FAMILY + "', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";


/* ===== ANIMAÇÃO (o generator substitui) ===== */
const ANIM_SPINS = 5;            // voltas completas antes de parar
const ANIM_SPEED = 1;          // 1.0 = 100%

/* ===== COOLDOWN MANAGEMENT MELHORADO ===== */
let spinCooldown = false;
let cooldownTimer = null;
const SPIN_COOLDOWN_MS = 5000;//20000;

/* ===== VARIÁVEIS DE LIMITE ===== */
let limitTimerInterval = null;
let isLimitExceeded = false;

function updateCooldownUI(seconds) {
  const container = document.getElementById('cooldownContainer');
  const message = document.getElementById('cooldownMessage');
  const wheelbox = document.getElementById('wheelbox');
  
  if (seconds > 0) {
    // Modo cooldown ativo
    wheelbox.classList.add('disabled');
    
    // Mostra com animação suave
    container.style.display = 'block';
    message.textContent = `Aguarde ${seconds}s`;
    
    // Força reflow para garantir a animação
    void message.offsetWidth;
    message.classList.add('show');
    message.classList.remove('warning');
    
  } else {
    // Cooldown finalizado - animação de saída suave
    message.classList.remove('show', 'warning');
    
    // Espera a transição terminar antes de esconder
    setTimeout(() => {
      container.style.display = 'none';
      wheelbox.classList.remove('disabled');
      spinCooldown = false;
    }, 300); // Tempo da transição CSS
  }
}

function showWarningMessage() {
  const container = document.getElementById('cooldownContainer');
  const message = document.getElementById('cooldownMessage');
  
  // Mostra imediatamente
  container.style.display = 'block';
  message.textContent = 'Aguarde para girar novamente';
  message.classList.add('show', 'warning');
  
  // Some após 1.5 segundos com animação suave
  setTimeout(() => {
    if (spinCooldown) {
      // Volta para mensagem normal de cooldown
      const remaining = Math.ceil((cooldownEndTime - Date.now()) / 1000);
      if (remaining > 0) {
        message.textContent = `Aguarde ${remaining}s`;
        message.classList.remove('warning');
      }
    } else {
      // Esconde com animação
      message.classList.remove('show', 'warning');
      setTimeout(() => {
        container.style.display = 'none';
      }, 300);
    }
  }, 1500);
}

let cooldownEndTime = 0;

function startCooldown() {
  spinCooldown = true;
  cooldownEndTime = Date.now() + SPIN_COOLDOWN_MS;
  
  // Atualiza UI imediatamente
  updateCooldownUI(Math.ceil(SPIN_COOLDOWN_MS / 1000));
  
  // Limpa timer anterior se existir
  if (cooldownTimer) clearInterval(cooldownTimer);
  
  // Atualiza o contador a cada segundo
  cooldownTimer = setInterval(() => {
    const remaining = Math.ceil((cooldownEndTime - Date.now()) / 1000);
    
    if (remaining > 0) {
      updateCooldownUI(remaining);
    } else {
      clearInterval(cooldownTimer);
      updateCooldownUI(0);
    }
  }, 1000);
}

// ✅ Garantir que está escondido no início
document.addEventListener('DOMContentLoaded', function() {
  const container = document.getElementById('cooldownContainer');
  const message = document.getElementById('cooldownMessage');
  
  container.style.display = 'none';
  message.classList.remove('show', 'warning');
});

// --- Audio guard: evita NotSupportedError quando não há fonte carregada ---
(function(){
  try {
    var P = (typeof HTMLMediaElement!=='undefined') ? HTMLMediaElement.prototype : null;
    if (!P || P.__spinPatched) return;
    var _play = P.play;
    P.play = function(){
      try {
        var hasSrc = !!(this.currentSrc || this.src || (this.querySelector && this.querySelector('source') && this.querySelector('source').src));
        if (!hasSrc) return Promise.resolve();
        var ret = _play.apply(this, arguments);
        if (ret && typeof ret.catch==='function') ret.catch(function(){});
        return ret;
      } catch(e){ return Promise.resolve(); }
    };
    P.__spinPatched = true;
  } catch(_) {}
})();
// ---------------------------------------------------------------------------

/* ===== POINTER (gerado) ===== */
const POINTER_COLOR = "#000000";
const POINTER_POSITION = "top";
/* ===== POSIÇÃO TEXTO/IMAGEM ===== */
const IMG_R_POS  = 0.90; // imagem mais para a borda (0..1)*r
const TEXT_R_POS = 0.50;
const TEXT_CENTER_POS = 0.70; // posição radial quando NÃO há imagem

/* ===== IMAGEM DO SEGMENTO ===== */
const IMAGE_STYLE = 'slice'; // 'slice' | 'ring'

// ===== API base dinâmica (antes de /public/) =====
(function(){
  const here = location.pathname;                  // ex: /roulette-admin-v6/public/roletas/teste.php
  const m = here.match(/^(.*)\/public\//);
  const base = m ? m[1] : '';
  window.API_SPIN = base + '/api/spin.php';
  window.API_ACK  = base + '/api/spin_ack.php';
  // compat: se o template ainda usa identificadores "API_SPIN"/"API_ACK"
  window.API_SPIN && (window.API_SPIN === window.API_SPIN);
  window.API_ACK  && (window.API_ACK  === window.API_ACK);
  // ROULETTE_ID será injetado pelo generator após ANIM_SPEED
})();

const MAX_WAIT_RESULT = 6.0; // s esperando o resultado do servidor antes de fallback

/* helpers */
function shade(hex, pct){
  hex = String(hex||'').trim();
  if(!/^#?[0-9a-f]{6}$/i.test(hex)) return '#A855F7';
  if(hex[0] !== '#') hex = '#'+hex;
  const n = parseInt(hex.slice(1), 16);
  let r=(n>>16)&255, g=(n>>8)&255, b=n&255;
  const f = (100+pct)/100;
  r = Math.max(0, Math.min(255, Math.round(r*f)));
  g = Math.max(0, Math.min(255, Math.round(g*f)));
  b = Math.max(0, Math.min(255, Math.round(b*f)));
  const toHex = v => v.toString(16).padStart(2,'0');
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function parseHex(hex){
  hex = String(hex||'').trim();
  if(hex[0] !== '#') hex = '#'+hex;
  if(!/^#[0-9a-f]{6}$/i.test(hex)) hex = '#A855F7';
  const n = parseInt(hex.slice(1),16);
  return { r:(n>>16)&255, g:(n>>8)&255, b:n&255, hex };
}
function escHtml(s){ return String(s||'').replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m])); }
function normalizeUrl(u){ u=(u||'').trim(); if(!u) return ''; if(/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(u)) return u; return 'https://'+u.replace(/^\/+/, ''); }

/* ===== OFFSET do ponteiro ===== */
function getPointerOffset(pos){
  switch(String(pos).toLowerCase()){
    case 'bottom': return Math.PI/2;
    case 'left':   return Math.PI;
    case 'top':    return Math.PI*1.5;
    case 'right':
    default:       return 0;
  }
}
let POINTER_OFFSET = getPointerOffset(POINTER_POSITION);

/* constantes */
const N = PRIZES.length || 8;
const TAU = Math.PI*2, SLICE = TAU/N;

/* ===== ✅ ANIMAÇÃO MELHORADA: PARADA MAIS NATURAL E SUAVE ===== */
const TARGET_SAFETY_FRAC = 0.015;     // ✅ Reduzido para 1.5% - parada mais precisa
const TARGET_SAFETY = SLICE * TARGET_SAFETY_FRAC;
const MICRO_MAX_FRAC = 0.08;          // ✅ Reduzido para 8% - correção mais sutil
const MICRO_MAX = SLICE * MICRO_MAX_FRAC; 
const MICRO_TIME = 1.2;               // ✅ Aumentado para 1.2s - correção ultra suave
const MIN_DECEL_TIME   = 2.0;         // ✅ Aumentado para 2.0s - desaceleração mais longa
const MIN_DECEL_TURNS  = 0.6;         // ✅ Aumentado para 0.6 voltas - mais espaço para frenagem
const MAX_EXTRA_TURNS  = 2;           // ✅ Reduzido para 2 voltas extras máximas
const SPINS_PROMISED   = Math.max(0, +ANIM_SPINS || 0);
const DECEL_LEAD_SPINS = 0.08;        // ✅ Reduzido lead time

/* ===== ✅ PARÂMETROS DE ANIMAÇÃO MAIS SUAVES ===== */
const BASE_MAX_SPEED = 8.5;           // ✅ Reduzido velocidade máxima
const BASE_ACCEL = 18.0;              // ✅ Reduzido aceleração
const SPD = Math.max(0.25, Math.min(3.0, +ANIM_SPEED || 1.0));
const MAX_SPEED = BASE_MAX_SPEED * SPD;
const ACCEL     = BASE_ACCEL     * SPD;
const POST_AUDIO_CRUISE_T = 0.45, MIN_TOTAL_CRUISE_T = 0.40, MAX_WAIT_FOR_AUDIO = 1.80;

/* refs */
const titleEl=document.getElementById('title');
const subtitleEl=document.getElementById('subtitle');
const wheelbox=document.getElementById('wheelbox');
const wheel=document.getElementById('wheel'), ctx=wheel.getContext('2d');
const fx=document.getElementById('fx'), fxx=fx.getContext('2d');
const fxBG=document.getElementById('fxBG'), fxb=fxBG.getContext('2d');
const pointer=document.getElementById('pointer');

const bgm=document.getElementById('bgm'), tick=document.getElementById('tick');
const inicio=document.getElementById('inicio'), ganhou=document.getElementById('ganhou'), perdeu=document.getElementById('perdeu');

const resultModal=document.getElementById('resultModal');
const modalBackdrop=document.getElementById('modalBackdrop');
const modalClose=document.getElementById('modalClose');
const modalCTA=document.getElementById('modalCTA');
const fxModal=document.getElementById('fxModal'), fxm=fxModal.getContext('2d');
const modalQtyBig=document.getElementById('modalQtyBig');
const modalUnit=document.getElementById('modalUnit');
const modalH2=document.getElementById('modalH2');
const modalDetail=document.getElementById('modalDetail');
const modalBubble=document.getElementById('modalBubble');
const modalGame=document.getElementById('modalGame');
const modalGameInBubble=document.getElementById('modalGameInBubble');

/* estado */
let muted=false;
let modalIsWin=false;
let phase='idle', speed=0, currentAngle=0, lastT=null, spinIndex=null;
let cruiseClock=0, postAudioClock=0, audioEnded=false;
let anglePath=0; let diagClock=0; const DIAG=true;
let decelStartAngle=0, decelTargetAngle=0, decelDistance=0, decelTime=0, decelClock=0, decelAccel=0;
let microTargetAngle=0, microClock=0;
let lastTickSector=null;
let confetti=[]; let modalPrevT=null;
let resultTimeoutClock=0; let resultPromise=null;

/* layout */
const WHEEL_SCALE_PCT = 100; // 40..100 (100 = tamanho atual)

/* >>> NOVO: boosts por breakpoint */
const WHEEL_MOBILE_BOOST_PCT = 105;
const WHEEL_TABLET_BOOST_PCT = 104;
const WHEEL_DESKTOP_BOOST_PCT = 100;

function fitWheelToViewport(){
  const vw = Math.min(window.innerWidth, document.documentElement.clientWidth || window.innerWidth);
  const vh = Math.min(window.innerHeight, document.documentElement.clientHeight || window.innerHeight);
  const bodyStyles = getComputedStyle(document.body);
  const padH = parseFloat(bodyStyles.paddingLeft)+parseFloat(bodyStyles.paddingRight);
  const padV = parseFloat(bodyStyles.paddingTop)+parseFloat(bodyStyles.paddingBottom);
  const headerH = titleEl.offsetHeight + (subtitleEl?.offsetHeight||0);
  const safety=8;
  const availableW = Math.max(160, vw - padH - safety);
  const availableH = Math.max(160, vh - padV - headerH - 10);
  const base  = Math.max(220, Math.min(availableW, availableH, 1120));
  const factor = (vw <= 480) ? 0.92 : (vw <= 768 ? 0.86 : 0.74);

  const __sclRaw = (typeof WHEEL_SCALE_PCT==='number' ? WHEEL_SCALE_PCT : parseFloat(WHEEL_SCALE_PCT))/100;
  const __scl = Math.max(0.70, Math.min(1.50, __sclRaw));

  const boost = (vw <= 480)
    ? (WHEEL_MOBILE_BOOST_PCT/100)
    : (vw <= 768 ? (WHEEL_TABLET_BOOST_PCT/100) : (WHEEL_DESKTOP_BOOST_PCT/100));

  let __size = base * factor * __scl * boost;
  __size = Math.max(220, Math.min(__size, 1120));
  document.documentElement.style.setProperty('--wheel-size', (__size) + 'px');

  try{
    const ptrSize = Math.max(28, Math.min(72, __size * 0.085));
    const ptrInset = Math.max(18, Math.min(48, ptrSize * 0.62));
    document.documentElement.style.setProperty('--pointer-size',  ptrSize + 'px');
    document.documentElement.style.setProperty('--pointer-inset', ptrInset + 'px');
  }catch(e){}

  try{
    console.groupCollapsed('WheelScale');
    console.log('WHEEL_SCALE_PCT =', WHEEL_SCALE_PCT);
    console.log({vw, vh, base, factor, __scl, __size});
    console.log('--pointer-size  =', getComputedStyle(document.documentElement).getPropertyValue('--pointer-size').trim());
    console.log('--pointer-inset =', getComputedStyle(document.documentElement).getPropertyValue('--pointer-inset').trim());
    console.log('--wheel-size    =', getComputedStyle(document.documentElement).getPropertyValue('--wheel-size').trim());
    console.groupEnd();
  }catch(e){}
}

/* util */
const dpr=()=>Math.max(1, window.devicePixelRatio||1);
function resizeCanvas(c){
  const r=c.getBoundingClientRect(); const dw=Math.round(r.width*dpr()), dh=Math.round(r.height*dpr());
  if(dw<10||dh<10){ requestAnimationFrame(()=>resizeCanvas(c)); return false; }
  if(c.width!==dw||c.height!==dh){ c.width=dw; c.height=dh; return true; }
  return false;
}

/* ✅ CORREÇÃO: Normalização robusta para garantir consistência */
function robustNormalize(angle) {
  angle = angle % TAU;
  if (angle < 0) angle += TAU;
  return angle;
}

const normalize = robustNormalize; // Compatibilidade

/* ===== ✅ CORREÇÃO: Função de diferença angular melhorada ===== */
function shortestDelta(from, to) {
  let d = robustNormalize(to - from);
  if (d > Math.PI) d -= TAU;
  return d;
}

/* ===== ✅ CORREÇÃO: FALLBACK QUE RESPEITA O PATTERN ===== */
function fallbackWeightedPick() {
  console.warn('[SPIN] Using fallback pick - server response timeout');
  
  // Se temos um forced step do servidor, respeitamos ele
  const forced = window.__forcedStep || '';
  
  if (forced === 'L') {
    // ✅ FORÇADO LOSE: Busca apenas segmentos LOSE
    const loseSegments = PRIZES.filter(p => {
      const type = String(p.type || p.kind || '').toUpperCase();
      return type === 'LOSE';
    });
    
    if (loseSegments.length > 0) {
      // Sorteio ponderado entre LOSE
      const weights = loseSegments.map(p => Math.max(1, p.weight));
      const total = weights.reduce((a, b) => a + b, 0);
      
      if (total > 0) {
        let r = Math.random() * total;
        for (let i = 0; i < loseSegments.length; i++) {
          r -= weights[i];
          if (r <= 0) return PRIZES.indexOf(loseSegments[i]);
        }
      }
      return PRIZES.indexOf(loseSegments[0]);
    }
  }
  else if (forced === 'W') {
    // ✅ FORÇADO WIN: Busca apenas segmentos WIN
    const winSegments = PRIZES.filter(p => {
      const type = String(p.type || p.kind || '').toUpperCase();
      return type === 'WIN';
    });
    
    if (winSegments.length > 0) {
      // Encontra o WIN com MAIOR PESO
      let maxWeight = -1;
      let maxSegment = null;
      
      winSegments.forEach(seg => {
        if (seg.weight > maxWeight) {
          maxWeight = seg.weight;
          maxSegment = seg;
        }
      });
      
      if (maxSegment) return PRIZES.indexOf(maxSegment);
      return PRIZES.indexOf(winSegments[0]);
    }
  }

  // ✅✅✅ FALLBACK DE EMERGÊNCIA: Sorteio normal considerando pesos
  const validSegments = PRIZES.filter(p => p.weight > 0);
  if (validSegments.length === 0) return 0;
  
  const weights = validSegments.map(p => p.weight);
  const total = weights.reduce((a, b) => a + b, 0);
  
  if (total <= 0) return Math.floor(Math.random() * validSegments.length);
  
  let r = Math.random() * total;
  for (let i = 0; i < validSegments.length; i++) {
    r -= weights[i];
    if (r <= 0) return PRIZES.indexOf(validSegments[i]);
  }
  
  return PRIZES.indexOf(validSegments[validSegments.length - 1]);
}

function sectorAngleMid(i){ return i*SLICE + SLICE/2; }
function centerAngleForIndex(i){
  const mid = sectorAngleMid(i);
  return normalize(-mid + POINTER_OFFSET);
}

function maybeTick(angle){
  const sector = Math.floor(normalize((angle - POINTER_OFFSET)) / SLICE);
  if(lastTickSector!==sector){
    lastTickSector=sector;
    if(!muted){ try{ tick.currentTime=0; tick.play(); }catch{} }
    pointer.classList.remove('bump'); void pointer.offsetWidth; pointer.classList.add('bump');
  }
}

/* imagens por segmento */
const SEG_IMAGES = new Array(PRIZES.length).fill(null);
PRIZES.forEach((p, i) => {
  if (!p.image) return;
  const img = new Image();
  img.decoding = 'async'; img.loading = 'eager'; img.src = p.image;
  img.onload = () => { SEG_IMAGES[i] = img; if (typeof drawWheel === 'function') drawWheel(currentAngle); };
  img.onerror = () => { SEG_IMAGES[i] = null; };
});

/* desenho da roda */

/* === Helpers: clip annular wedge + draw image into sector === */
function clipAnnularWedge(ctx, inner, outer, a0, a1){
  ctx.beginPath();
  ctx.arc(0,0, outer, a0, a1, false);
  ctx.arc(0,0, inner, a1, a0, true);
  ctx.closePath();
  ctx.clip();
}
function drawWheel(angle){
  resizeCanvas(wheel);
  const w=wheel.width,h=wheel.height,cx=w/2,cy=h/2;
  const r=Math.min(w,h)*0.40;
  const SL=SLICE;

  ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0,0,w,h);
  ctx.translate(cx,cy); ctx.rotate(angle);
    // ===== Helpers locais e flag de viewport (wrap só fora do mobile) =====
    const vw   = Math.min(window.innerWidth, document.documentElement.clientWidth || window.innerWidth);
    // USA O CONTAINER REAL EM VEZ DA JANELA:
    const boxW = (wheelbox && wheelbox.clientWidth) ? wheelbox.clientWidth : vw;
    const IS_MOBILE = boxW <= 480;  // antes usava: vw <= 480

  function maxTextWidthAtRadius(radius, sliceAngle, padding = 8){
    const chord = 2 * radius * Math.tan(sliceAngle / 2);
    return Math.max(0, chord - padding * 2);
  }

  // Quebra APENAS entre palavras (não corta sílabas), em até 2 linhas
  function wrapByWords(ctx, text, maxWidth){
    const raw = String(text || '').trim();
    if (!raw) return [];
    if (ctx.measureText(raw).width <= maxWidth) return [raw];
    const words = raw.split(/\s+/);
    const lines = [];
    let line = '';
    for (let i=0; i<words.length; i++){
      const test = line ? line + ' ' + words[i] : words[i];
      if (ctx.measureText(test).width > maxWidth && line){
        lines.push(line);
        line = words[i];
      } else {
        line = test;
      }
    }
    if (line) lines.push(line);
    if (lines.length <= 2) return lines;
    const mid = Math.ceil(lines.length / 2);
    return [ lines.slice(0,mid).join(' '), lines.slice(mid).join(' ') ];
  }


  // Desenha o aro da roleta
  const outer=r+Math.max(42, r*0.1), inner=r+Math.max(16, r*0.04);
  const g1=ctx.createRadialGradient(0,0,inner,0,0,outer+10);g1.addColorStop(0,'#c3cad6'); g1.addColorStop(0.55,'#7b8396'); g1.addColorStop(1,'#3b4356');
  ctx.lineWidth=outer-inner; ctx.strokeStyle=g1; ctx.beginPath(); ctx.arc(0,0,(outer+inner)/2,0,TAU); ctx.stroke();

  ctx.lineWidth=Math.max(6, r*0.025); ctx.strokeStyle='#e8ecf4'; ctx.beginPath(); ctx.arc(0,0,r+10,0,TAU); ctx.stroke();

  // Desenha os segmentos
  for(let i=0;i<N;i++){
    const a0=i*SL, a1=a0+SL, mid=a0+SL/2, p=PRIZES[i]||{};
    const hideText = p.hide_text_all || 0; // CORREÇÃO: Cada segmento tem seu próprio hide_text_all
    
    ctx.save();
    ctx.beginPath(); ctx.moveTo(0,0); ctx.arc(0,0,r,a0,a1); ctx.closePath(); ctx.clip();

    // Fundo do segmento
    const baseCol=(p.color||'#ddd'); ctx.fillStyle=baseCol; ctx.fillRect(-r,-r,r*2,r*2);

    const img = SEG_IMAGES[i];
    const hasImg = !!img;
    
    // ===== IMAGEM CONTIDA ACIMA DO TEXTO =====
    if (hasImg){
      const fit = String(p.imageFit||'contain').toLowerCase();
      const alpha = Math.max(0, Math.min(100, +p.imageOpacity||95))/100;
      const aMid = (a0 + a1) / 2;

      if (IMAGE_STYLE === 'ring'){
        // Estilo anel (mantido original) - removido fundo branco
        const rInner = Math.max(0, r * 0.27 - 2);
        const rOuter = r;
        const bandH  = Math.max(2, rOuter - rInner);
        const bias   = (typeof p.imageBiasIn === 'number'
                        ? Math.max(0, Math.min(1, p.imageBiasIn))
                        : 0.40);
        const rc     = rInner + bandH * bias;
        const arcLen = Math.max(2, 2 * rc * Math.tan(SL/2));

        const iw = img.naturalWidth || img.width, ih = img.naturalHeight || img.height;
        const ir = iw/ih, tr = arcLen/bandH;
        let sx=0, sy=0, sw=iw, sh=ih;
        if ((fit === 'cover'  && ir < tr) || (fit === 'contain' && ir > tr)) { sw = ih * tr; sx = (iw - sw) * 0.5; }
        else { sh = iw / tr; sy = (ih - sh) * 0.5; }

        ctx.save();
        (function clipAnnularWedge(ctx, inner, outer, a0, a1){
          ctx.beginPath();
          ctx.arc(0,0, outer, a0, a1, false);
          ctx.arc(0,0, inner, a1, a0, true);
          ctx.closePath();
          ctx.clip();
        })(ctx, rInner, rOuter, a0+0.003, a1-0.003);

        ctx.rotate(aMid);
        ctx.translate(rc, 0);
        ctx.rotate(Math.PI/2);

        const prevA = ctx.globalAlpha; ctx.globalAlpha = alpha;
        ctx.drawImage(img, sx, sy, sw, sh, -arcLen/2, -bandH/2, arcLen, bandH);
        ctx.globalAlpha = prevA;

        ctx.restore();
      } else {
        // ===== IMAGEM SLICE - AJUSTE CONFORME hide_text_all =====
        let boxW, boxH, rPos;

        if (hideText) {
          // Quando texto oculto: imagem ocupa quase todo o segmento
          boxW = Math.min(r * 0.95, (a1-a0) * r * 0.90);
          boxH = r * 0.70; // Altura maior
          rPos = r * 0.65; // Posição mais central
        } else {
          // Com texto: imagem menor acima do texto
          boxW = Math.min(r*0.80, (a1-a0)*r*0.85);
          boxH = r * 0.35;
          rPos = r * 0.80;
        }

        const iw = img.naturalWidth || img.width, ih = img.naturalHeight || img.height;
        const ir = iw/ih;
        
        let dw, dh, dx, dy;
        
        // SEMPRE usa contain para imagem inteira sem cortes
        if (ir > boxW/boxH) {
          dw = boxW;
          dh = dw / ir;
        } else {
          dh = boxH;
          dw = dh * ir;
        }
        dx = -dw/2;
        dy = -dh/2;

        ctx.save();
        ctx.rotate(aMid);
        ctx.translate(rPos, 0);
        ctx.rotate(Math.PI/2);

        const prevA = ctx.globalAlpha; ctx.globalAlpha = alpha;
        ctx.drawImage(img, dx, dy, dw, dh);
        ctx.globalAlpha = prevA;
        ctx.restore();
      }
    }

    // Sombra no segmento
    const ring=ctx.createRadialGradient(0,0,r*0.85,0,0,r);
    ring.addColorStop(0,'rgba(0,0,0,0)'); ring.addColorStop(1,'rgba(0,0,0,.25)');
    ctx.fillStyle=ring; ctx.fillRect(-r,-r,r*2,r*2);
    ctx.restore();

    // Divisória entre segmentos
    ctx.beginPath(); ctx.lineWidth=Math.max(4, r*0.02); ctx.strokeStyle='rgba(255,255,255,.95)';
    ctx.arc(0,0,r,a0,a0+0.0025); ctx.stroke();
  }

  // ===== TEXTO (somente se não estiver oculto) =====
  for(let i=0;i<N;i++){
    const mid=i*SL+SL/2;
    const p=PRIZES[i]||{};
    const hasImg = !!SEG_IMAGES[i];
    const hideText = p.hide_text_all || 0; // CORREÇÃO: Cada segmento tem seu próprio hide_text_all

    // Se texto oculto, pule este segmento
    if (hideText) continue;

    // Mesma posição radial do template
    const textR = hasImg ? r * TEXT_R_POS : r * TEXT_CENTER_POS;

    ctx.save();
    ctx.rotate(mid);
    ctx.translate(textR,0);
    ctx.rotate(Math.PI/2);
    ctx.textAlign='center';
    ctx.textBaseline='middle';

    // === Estilos do texto do segmento ===
    const text_color = p.text_color || '#ffffff';
    ctx.fillStyle = text_color;

    // === Mobile font boost (reaproveita IS_MOBILE já definido no topo da drawWheel) ===
    const __IS_MOBILE    = IS_MOBILE;
    const __PRIMARY_BOOST = 1.15; // ex.: +15% para a linha de cima (primary) no mobile
    const __UNIT_BOOST    = 1.25; // ex.: +25% para a linha de baixo (unit) no mobile

    const text_size = Math.round(
      (p.text_size || Math.max(24, Math.round(r * 0.09))) *
      (__IS_MOBILE ? __PRIMARY_BOOST : 1)
    );

    const unit_text_size = Math.round(
      (p.unit_text_size || Math.max(13, Math.round(r * 0.045))) *
      (__IS_MOBILE ? __UNIT_BOOST : 1)
    );

    ctx.fillStyle = text_color;

    // PRIMARY sempre 1 linha (como original)
    ctx.font = `900 ${text_size}px ${FONT_STACK}`;
    ctx.shadowColor='rgba(0,0,0,0.6)';
    ctx.shadowBlur=4;
    ctx.shadowOffsetX=1;
    ctx.shadowOffsetY=1;
    ctx.strokeStyle='rgba(0,0,0,0.8)';
    ctx.lineWidth=Math.max(4, r*0.018);

    const primaryY = hasImg ? -6 : -6;
    if (p.primary){
      ctx.strokeText(p.primary, 0, primaryY);
      ctx.fillText  (p.primary, 0, primaryY);
    }

    // UNIT: quebra por palavras só se necessário e só fora do mobile
    const unit = String(p.unit || '').trim();
    if (unit){
      ctx.font = `800 ${unit_text_size}px ${FONT_STACK}`;
      ctx.strokeStyle='rgba(0,0,0,0.8)';
      ctx.lineWidth=Math.max(3, r*0.015);

      const pad = Math.max(6, r*0.02);
      const maxW = maxTextWidthAtRadius(textR, SL, pad);
      const yCenter = hasImg ? 18 : 16;

      if (!IS_MOBILE && ctx.measureText(unit).width > maxW && unit.includes(' ')){
        const lh = Math.round(unit_text_size * 1.12);
        const lines = wrapByWords(ctx, unit, maxW);
        if (lines.length === 1){
          ctx.strokeText(lines[0], 0, yCenter);
          ctx.fillText  (lines[0], 0, yCenter);
        } else {
          // duas linhas: centraliza ao redor do yCenter
          const y1 = yCenter - lh/2;
          const y2 = yCenter + lh/2;
          ctx.strokeText(lines[0], 0, y1);
          ctx.fillText  (lines[0], 0, y1);
          ctx.strokeText(lines[1], 0, y2);
          ctx.fillText  (lines[1], 0, y2);
        }
      } else {
        // mobile ou coube em 1 linha
        ctx.strokeText(unit, 0, yCenter);
        ctx.fillText  (unit, 0, yCenter);
      }
    }

    // Limpa sombras
    ctx.shadowColor='transparent';
    ctx.shadowBlur=0;
    ctx.shadowOffsetX=0;
    ctx.shadowOffsetY=0;

    ctx.restore();
  }

  // ===== BOTÃO CENTRAL =====
  const cr=r*0.27; ctx.rotate(-angle);ctx.beginPath(); ctx.arc(0,0,cr,0,TAU); ctx.fillStyle='#f59e0b'; ctx.fill();ctx.lineWidth=Math.max(2,cr*0.075); ctx.strokeStyle='rgba(255,255,255,.92)'; ctx.beginPath(); ctx.arc(0,0,cr-ctx.lineWidth*0.55,0,TAU); ctx.stroke();ctx.textAlign='center'; ctx.textBaseline='middle';ctx.shadowColor='transparent'; ctx.shadowBlur=0; ctx.shadowOffsetX=0; ctx.shadowOffsetY=0;const spinSize = 24;ctx.font=`900 ${spinSize}px ${FONT_STACK}`;const __txtColor = '#ffffff';const __tc=__txtColor.replace('#','');const __tr=parseInt(__tc.substring(0,2),16), __tg=parseInt(__tc.substring(2,4),16), __tb=parseInt(__tc.substring(4,6),16);const __tl=0.2126*__tr+0.7152*__tg+0.0722*__tb;const __haloMain=(__tl>160)?'rgba(0,0,0,0.70)':'rgba(255,255,255,0.90)';const __haloSub =( __tl>160)?'rgba(0,0,0,0.34)':'rgba(255,255,255,0.42)';ctx.lineJoin='round'; ctx.miterLimit=2;ctx.strokeStyle=__haloMain; ctx.lineWidth=Math.max(2, r*0.014); ctx.strokeText(SPIN_TEXT,0,0);ctx.strokeStyle=__haloSub;  ctx.lineWidth=Math.max(1, r*0.008); ctx.strokeText(SPIN_TEXT,0,0);ctx.fillStyle=__txtColor; ctx.fillText(SPIN_TEXT,0,0);
}
/* FX: LEDs + halo com a cor do ponteiro */
function drawLeds(){
  resizeCanvas(fx);
  const W=fx.width,H=fx.height,cx=W/2,cy=H/2;
  const r=Math.min(wheel.width,wheel.height)*0.40;
  const outer=r+44, inner=r+18;
  fxx.setTransform(1,0,0,1,0,0); fxx.clearRect(0,0,W,H);
  const t=performance.now()/1000;
  const bulbs=Math.max(72, Math.round(r*0.68));
  const bulbs2=Math.max(56, Math.round(r*0.52));
  for(let i=0;i<bulbs;i++){ const a=i/bulbs*TAU,x=cx+Math.cos(a)*outer,y=cy+Math.sin(a)*outer,k=(Math.cos(a - t*2.1)*.5+.5),sz=2.4+k*2.4;
    fxx.beginPath(); fxx.fillStyle=`rgba(255,240,200,${0.26+k*0.66})`; fxx.shadowColor='rgba(255,230,170,.85)'; fxx.shadowBlur=10+k*10; fxx.arc(x,y,sz,0,TAU); fxx.fill(); }
  for(let i=0;i<bulbs2;i++){ const a=i/bulbs2*TAU,x=cx+Math.cos(a)*inner,y=cy+Math.sin(a)*inner,k=(Math.cos(a + t*2.9)*.5+.5),sz2=2.0+k*2.0;
    fxx.beginPath(); fxx.fillStyle=`rgba(180,220,255,${0.20+k*0.48})`; fxx.shadowColor='rgba(200,220,255,.85)'; fxx.shadowBlur=9+k*9; fxx.arc(x,y,sz2,0,TAU); fxx.fill(); }
}
function drawFxBG(){
  resizeCanvas(fxBG);
  const W=fxBG.width,H=fxBG.height,cx=W/2,cy=H/2;
  const r=Math.min(wheel.width,wheel.height)*0.40;
  fxb.setTransform(1,0,0,1,0,0); fxb.clearRect(0,0,W,H);
  const outerR=Math.max(W,H)*0.55;

  const {r:rr,g:rg,b:rb} = parseHex(POINTER_COLOR);
  const halo=fxb.createRadialGradient(cx,cy,r*0.9,cx,cy,outerR);
  halo.addColorStop(0, `rgba(${rr},${rg},${rb},.12)`);
  halo.addColorStop(1, 'rgba(0,0,0,0)');
  fxb.fillStyle=halo; fxb.beginPath(); fxb.arc(cx,cy,outerR,0,TAU); fxb.fill();
}

/* modal & confete */
function openModal(){ resultModal.classList.add('open'); confetti=[]; modalPrevT=null; }
function closeModal(){ resultModal.classList.remove('open'); confetti=[]; modalPrevT=null; }
modalBackdrop.onclick = modalClose.onclick = ()=>closeModal();
function drawFxModal(){
  resizeCanvas(fxModal);
  const W=fxModal.width,H=fxModal.height;
  const t=performance.now()/1000;
  const dt=modalPrevT?Math.min(0.033,t-modalPrevT):1/60; modalPrevT=t;
  fxm.setTransform(1,0,0,1,0,0); fxm.clearRect(0,0,W,H);
  if(!resultModal.classList.contains('open') || !modalIsWin) return;
  const SPAWN=160, spawn=Math.floor(SPAWN*dt);
  for(let i=0;i<spawn;i++){
    const s=4+Math.random()*3;
    confetti.push({x:Math.random()*W,y:-10,vxBase:(Math.random()*2-1)*20,vy:80+Math.random()*180,rot:Math.random()*TAU,vr:(Math.random()*2-1)*4,w:s,h:s*2,hue:Math.floor(Math.random()*360),seed:Math.random()*TAU});
  }
  const G=40;
  for(let i=confetti.length-1;i>=0;i--){
    const p=confetti[i], sway=Math.sin((t+p.seed)*4)*30, vx=p.vxBase+sway;
    p.vy+=G*dt; p.x+=vx*dt; p.y+=p.vy*dt; p.rot+=p.vr*dt;
    fxm.save(); fxm.translate(p.x,p.y); fxm.rotate(p.rot); fxm.fillStyle=`hsla(${p.hue},95%,60%,.95)`; fxm.fillRect(-p.w/2,-p.h/2,p.w,p.h); fxm.restore();
    if(p.y>H+24) confetti.splice(i,1);
  }
}

/* ======================= SISTEMA DE LIMITE EXCEDIDO ======================= */

/**
 * Mostra o modal de limite excedido (hora/dia/sessão)
 */
function showLimitModal(errorData) {
  const modal = document.getElementById('limitModal');
  const title = document.getElementById('limitTitle');
  const message = document.getElementById('limitMessage');
  const currentCount = document.getElementById('limitCurrentCount');
  const maxCount = document.getElementById('limitMaxCount');
  const limitType = document.getElementById('limitType');
  const timer = document.getElementById('limitTimer');
  const cta = document.getElementById('limitCTA');
  const backdrop = document.getElementById('limitBackdrop');

  // Preenche os dados
  title.textContent = 'Limite Atingido';
  message.textContent = errorData.message || 'Você atingiu o limite máximo de giros.';
  currentCount.textContent = errorData.current_count || 0;
  maxCount.textContent = errorData.max_limit || 0;
  limitType.textContent = errorData.limit_type || 'hora';

  // Inicia o timer
  startLimitTimer(errorData.retry_after || 60, timer);

  // Configura os eventos
  cta.onclick = backdrop.onclick = () => {
    modal.classList.remove('open');
    stopLimitTimer();
  };

  // Mostra o modal
  modal.classList.add('open');
  isLimitExceeded = true;
}

/**
 * Mostra o modal de limite TOTAL excedido
 */
function showTotalLimitModal(errorData) {
  const modal = document.getElementById('totalLimitModal');
  const title = document.getElementById('totalLimitTitle');
  const message = document.getElementById('totalLimitMessage');
  const currentCount = document.getElementById('totalLimitCurrentCount');
  const maxCount = document.getElementById('totalLimitMaxCount');
  const limitPeriod = document.getElementById('totalLimitPeriod');
  const timer = document.getElementById('totalLimitTimer');
  const info = document.getElementById('totalLimitInfo');
  const cta = document.getElementById('totalLimitCTA');
  const backdrop = document.getElementById('totalLimitBackdrop');

  // Preenche os dados
  title.textContent = 'Limite Total Atingido';
  message.textContent = errorData.message || 'Você atingiu o limite total de giros.';
  currentCount.textContent = errorData.current_count || 0;
  maxCount.textContent = errorData.max_limit || 0;
  
  // Define o período
  const periodText = errorData.period === 'forever' ? 'definitivos' : 
                    errorData.period === 'day' ? 'por dia' :
                    errorData.period === 'week' ? 'por semana' :
                    errorData.period === 'month' ? 'por mês' : 'totais';
  limitPeriod.textContent = periodText;

  // Se for bloqueio permanente
  if (!errorData.retry_after) {
    timer.textContent = 'BLOQUEADO';
    timer.style.color = '#dc2626';
    info.textContent = 'Seu acesso a esta roleta foi bloqueado permanentemente devido ao limite total excedido.';
  } else {
    // Se for bloqueio temporário
    startLimitTimer(errorData.retry_after, timer);
    info.textContent = 'Seu acesso será restaurado automaticamente quando o período atual terminar.';
  }

  // Configura os eventos
  cta.onclick = backdrop.onclick = () => {
    modal.classList.remove('open');
    stopLimitTimer();
  };

  // Mostra o modal
  modal.classList.add('open');
  isLimitExceeded = true;
}

/**
 * Inicia o timer do limite
 */
function startLimitTimer(totalSeconds, displayElement) {
  let remaining = totalSeconds;
  
  function updateTimer() {
    const minutes = Math.floor(remaining / 60);
    const seconds = remaining % 60;
    displayElement.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
    
    if (remaining <= 0) {
      stopLimitTimer();
      closeLimitModal();
      closeTotalLimitModal();
      // Recarrega a página para resetar os contadores
      setTimeout(() => location.reload(), 500);
    } else {
      remaining--;
    }
  }
  
  // Atualiza imediatamente e a cada segundo
  updateTimer();
  limitTimerInterval = setInterval(updateTimer, 1000);
}

/**
 * Para o timer do limite
 */
function stopLimitTimer() {
  if (limitTimerInterval) {
    clearInterval(limitTimerInterval);
    limitTimerInterval = null;
  }
}

/**
 * Fecha o modal de limite
 */
function closeLimitModal() {
  const modal = document.getElementById('limitModal');
  modal.classList.remove('open');
  isLimitExceeded = false;
}

/**
 * Fecha o modal de limite total
 */
function closeTotalLimitModal() {
  const modal = document.getElementById('totalLimitModal');
  modal.classList.remove('open');
  isLimitExceeded = false;
}

/**
 * Simula um giro de LOSE quando o limite é excedido
 */
function simulateLoseSpin(errorData) {
  console.log('🔒 Simulando giro de LOSE por limite excedido');

  // Verifica se é limite total
  if (errorData.code === 'total_limit_exceeded') {
    showTotalLimitModal(errorData);
  } else {
    // Limites normais (hora/dia/sessão)
    showLimitModal(errorData);
  }

  // Encontra segmentos do tipo LOSE
  const loseSegments = PRIZES.filter(p => {
    const type = String(p.type || p.kind || '').toUpperCase();
    return type === 'LOSE';
  });

  // Escolhe um segmento LOSE aleatório
  const randomIndex = Math.floor(Math.random() * loseSegments.length);
  const loseSegment = loseSegments[randomIndex];
  spinIndex = PRIZES.indexOf(loseSegment);

  // Inicia a animação normal
  startSpinAnimation();
}

/**
 * Inicia a animação do spin (separado da lógica do servidor)
 */
function startSpinAnimation() {
  phase = 'accelerate';
  speed = 0;
  cruiseClock = 0;
  postAudioClock = 0;
  audioEnded = false;
  anglePath = 0;
  diagClock = 0;
  resultTimeoutClock = 0;
  lastTickSector = null;

  // Áudio de início
  inicio.onended = () => { audioEnded = true; };
  if (!muted) {
    try {
      inicio.currentTime = 0;
      inicio.play().catch(() => { audioEnded = true; });
    } catch {
      audioEnded = true;
    }
    if (bgm.paused) {
      try { bgm.play(); } catch {}
    }
  } else {
    audioEnded = true;
  }
}

/* ======================= SINCRONIZAÇÃO COM SERVIDOR ======================= */
function findServerIndex(data){
  if (!data) return null;
  if (typeof data.index === 'number') return data.index;
  if (typeof data.chosen_index === 'number') return data.chosen_index;
  const cd = data.chosen_details || {};
  if (typeof cd.index === 'number') return cd.index;
  const id = (cd && (cd.id ?? cd.segment_id)) ?? (data.id ?? data.segment_id);
  if (id != null){
    const i = PRIZES.findIndex(p => String(p.id) === String(id));
    if (i >= 0) return i;
  }
  return null;
}

let __TOK = null; // tokens pro ACK

async function requestServerSpin(){
  try{
    const params = new URLSearchParams();
    params.set('roleta', String(window.ROULETTE_ID)); // << ESSENCIAL

    const res = await fetch(window.API_SPIN, {
      method:'POST',
      credentials:'same-origin',
      headers:{
        'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
        'Accept':'application/json',
        'X-Requested-With':'XMLHttpRequest'
      },
      body: params
    });

    const text = await res.text();
    let json; 
    try{ 
      json = JSON.parse(text); 
    } catch(e){ 
      throw new Error('Resposta não-JSON do servidor: '+text.slice(0,160)); 
    }

    // Se for erro de limite, lança exceção específica
    if (!json.ok && json.code && json.code.includes('limit_exceeded')) {
      throw json;
    }

    // Se outro tipo de erro
    if (!json.ok) {
      throw new Error(json.message || 'Erro no servidor');
    }

    // guarde tokens para ACK
    __TOK = { spin_id: json.spin_id, nonce: json.verify?.nonce, hmac: json.verify?.hmac };

    const idx = findServerIndex(json);
    return { idx, raw: json };
  } catch(e) {
    // Repassa o erro
    throw e;
  }
}

function ackResult(){
  try{
    if (__TOK && __TOK.spin_id && __TOK.nonce && __TOK.hmac){
      const body = new URLSearchParams();
      body.set('spin_id', String(__TOK.spin_id));
      body.set('nonce',   String(__TOK.nonce));
      body.set('hmac',    String(__TOK.hmac));
      if (typeof spinIndex === 'number') body.set('index', String(spinIndex));

      fetch(window.API_ACK, {
        method:'POST',
        credentials:'same-origin',
        headers:{
          'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
          'Accept':'application/json',
          'X-Requested-With':'XMLHttpRequest'
        },
        body
      }).catch(()=>{});
    }
  } catch(_){}
  __TOK = null;
}
/* ========================================================================== */

/* ===== ✅ ANIMAÇÃO MELHORADA: DESACELERAÇÃO MAIS NATURAL ===== */
function planDecelExact(){
  const targetMid = sectorAngleMid(spinIndex);
  const finalLocal = robustNormalize(-targetMid + POINTER_OFFSET);
  const curLocal = robustNormalize(currentAngle);

  // ✅ CORREÇÃO: Cálculo de delta mais preciso
  let delta = finalLocal - curLocal;
  if (delta < -Math.PI) delta += TAU;
  if (delta > Math.PI) delta -= TAU;
  if (delta < 0) delta += TAU;

  // quantas voltas prometidas restam (garante cumprir ANIM_SPINS)
  const doneSpins = Math.max(0, anglePath / TAU);
  let extraTurns  = Math.max(0, SPINS_PROMISED - doneSpins);

  // ✅ CORREÇÃO: Velocidade inicial mais suave para desaceleração
  const v0 = Math.max(MAX_SPEED * 0.35, Math.min(speed, MAX_SPEED * 0.75));

  // distância inicial a percorrer
  let D = delta + extraTurns * TAU;

  // ✅ CORREÇÃO: Frenagem mais natural com menos voltas extras
  let turnsAdded = 0;
  const needsTrail = () => (D < MIN_DECEL_TURNS * TAU);
  const needsTime  = () => ((2 * D / v0) < MIN_DECEL_TIME);

  while ((needsTrail() || needsTime()) && turnsAdded < MAX_EXTRA_TURNS) {
    D += TAU;
    turnsAdded++;
  }

  // ✅ CORREÇÃO: Tempo de desaceleração mais longo e suave
  const T = Math.max(MIN_DECEL_TIME, 2.2 * D / v0);

  decelStartAngle = currentAngle;
  decelTargetAngle = currentAngle + D - TARGET_SAFETY;
  decelDistance = D;
  decelTime = T;
  decelClock = 0;
  
  // ✅ CORREÇÃO: Aceleração de frenagem mais suave
  decelAccel = -v0 / T;

  console.log('[SPIN] planDecelExact - targetMid=%s°, delta=%s°, D=%s°, T=%ss, v0=%.2f, turnsAdded=%s',
    (targetMid * 180/Math.PI).toFixed(1),
    (delta * 180/Math.PI).toFixed(1),
    (D * 180/Math.PI).toFixed(1),
    T.toFixed(2),
    v0,
    turnsAdded
  );
}

/* ===== ✅ ANIMAÇÃO MELHORADA: ALINHAMENTO MAIS SUAVE ===== */
function ensureAlignedThenStop(){
  const normalizedAngle = robustNormalize(POINTER_OFFSET - currentAngle);
  const idxFromAngle = Math.floor(normalizedAngle / SLICE) % N;
  
  // ✅ CORREÇÃO: Tolerância maior para evitar micro-correções desnecessárias
  const tolerance = SLICE * 0.03; // 3% de tolerância
  
  if (Math.abs(idxFromAngle - spinIndex) > 0.5) {
    console.warn('[SPIN] Realinhando suavemente: ângulo=', (normalizedAngle * 180/Math.PI).toFixed(1) + '°, índice calculado=', idxFromAngle, 'índice servidor=', spinIndex);
    
    // Forçar o ângulo correto de forma mais suave
    const targetMid = sectorAngleMid(spinIndex);
    const targetAngle = robustNormalize(-targetMid + POINTER_OFFSET);
    
    // ✅ CORREÇÃO: Calcular diferença com tolerância
    let delta = targetAngle - robustNormalize(currentAngle);
    if (delta > Math.PI) delta -= TAU;
    if (delta < -Math.PI) delta += TAU;
    
    // ✅ CORREÇÃO: Só aplica correção se realmente necessário
    if (Math.abs(delta) > tolerance) {
      microTargetAngle = currentAngle + delta;
      microClock = 0;
      phase = 'micro-correct';
      console.log('[SPIN] Correção suave aplicada: delta=', (delta * 180/Math.PI).toFixed(2) + '°');
      return;
    } else {
      console.log('[SPIN] Dentro da tolerância, parando naturalmente');
    }
  }
  
  phase='idle'; 
  stopSpin();
}

function startSpin(){
  if(phase!=='idle') return;
  
  // ✅ CORREÇÃO: Reset mais agressivo do estado
  spinIndex = null;
  window.__forcedStep = '';
  
  startCooldown();

  // Faz a requisição ao servidor
  resultPromise = requestServerSpin()
    .then(({idx}) => {
      if (typeof idx === 'number' && idx >= 0 && idx < N){
        spinIndex = idx;
        console.log('[SPIN] server index =', spinIndex);
        startSpinAnimation();
      } else {
        console.warn('[SPIN] server did not return a valid index');
        startSpinAnimation(); // Inicia animação mesmo sem índice válido
      }
    })
    .catch(error => {
      console.warn('[SPIN] server error', error);
      
      // Se for erro de limite, simula um LOSE
      if (error.code && error.code.includes('limit_exceeded')) {
        simulateLoseSpin(error);
      } else {
        // Outros erros: fallback normal
        spinIndex = fallbackWeightedPick();
        startSpinAnimation();
      }
    });
}

/* ===== Função para calcular cor de contraste ===== */
function getContrastColor(hexColor) {
  // Remove o # se existir
  hexColor = hexColor.replace('#', '');
  
  // Converte para RGB
  const r = parseInt(hexColor.substr(0, 2), 16);
  const g = parseInt(hexColor.substr(2, 2), 16);
  const b = parseInt(hexColor.substr(4, 2), 16);
  
  // Calcula luminância (fórmula de contraste WCAG)
  const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
  
  // Retorna branco para cores escuras, preto para cores claras
  return luminance > 0.5 ? '#000000' : '#ffffff';
}

/* ✅ CORREÇÃO: Função stopSpin MELHORADA com verificação de alinhamento */
function stopSpin(){
  // Se foi um limite excedido, não mostra o modal normal
  if (isLimitExceeded) {
    console.log('[SPIN] Limite excedido - pulando modal normal');
    return;
  }

  try {
    const totalSpins = (anglePath/TAU);
    const diff = Math.abs((+ANIM_SPINS||0) - totalSpins);
    
    // ✅ CORREÇÃO: Verificação mais precisa do alinhamento
    const normalizedAngle = robustNormalize(POINTER_OFFSET - currentAngle);
    const idxFromAngle = Math.floor(normalizedAngle / SLICE) % N;
    
    console.log('[SPIN] Final - totalSpins=%s, angle=%s°, idxFromAngle=%s, spinIndex=%s, alinhado=%s',
      totalSpins.toFixed(3),
      (normalizedAngle * 180/Math.PI).toFixed(1),
      idxFromAngle,
      spinIndex,
      idxFromAngle === spinIndex
    );

    // ✅ CORREÇÃO: Só força realinhamento se realmente necessário
    if (idxFromAngle !== spinIndex && Math.abs(idxFromAngle - spinIndex) > 0.5) {
      console.warn('[SPIN] Pequeno desalinhamento detectado, mas dentro da tolerância aceitável');
    }

  } catch(_) {}

  const p = PRIZES[spinIndex] || {};
  const forced = String(window.__forcedStep || '').toUpperCase();
  const kind   = String(p.kind || p.type || '').toUpperCase();
  let   isWin  = (forced === 'L') ? false : (forced === 'W') ? true : (kind === 'WIN');

  // ... resto do código do modal permanece igual ...
  // --------- LIMPA / PREPARA UI ---------
  modalBubble.style.display = '';
  modalQtyBig.style.display = 'none';
  modalUnit.style.display   = 'none';
  modalH2.style.display     = 'block';
  modalGame.style.display   = 'none';
  modalGame.innerHTML       = '';
  modalGameInBubble.style.display = 'none';
  modalGameInBubble.textContent   = '';
  modalCTA.onclick = null;

  // --------- MODAL EXCLUSIVO PARA 'L' (pattern) ---------
  if (forced === 'L') {
    // Perdeu "forçado" — ignora dados do segmento e mostra tela fixa
    modalBubble.style.display = 'none';
    document.getElementById('modalTitle').textContent  = 'Ah, que pena…';
    modalDetail.textContent = 'Não foi dessa vez... Tente novamente!';
    modalCTA.textContent = 'Fechar';
    modalCTA.onclick = closeModal;

    openModal();
    try{ perdeu.currentTime = 0; perdeu.play(); }catch(_){}
    ackResult();
    window.__forcedStep = '';
    return;
  }

  // --------- FLUXO NORMAL / 'W' ---------
  // Se WIN, monta a UI de vitória
  if (isWin) {
    document.getElementById('modalTitle').textContent = 'Parabéns!';
    modalDetail.textContent = '';

    // Se label_primary for número, usa o "círculo grande"
    const qty = String(p.primary||'').trim();
    const isNumeric = /^\d+$/.test(qty);
    if (isNumeric){
      modalQtyBig.style.display = 'block';
      modalQtyBig.textContent = qty;
      modalUnit.style.display = 'block';
      modalUnit.textContent = String(p.unit||'GIROS');
      modalH2.style.display = 'none';
    } else {
      // Se não for numérico, mostra no detail
      modalDetail.textContent = `Você ganhou ${p.primary||''} ${p.unit||''}.`.replace(/\s+/g,' ').trim();
    }

    // ✅ COR DA BOLHA COM A COR DO SEGMENTO
    const base = (p.color || '#ef4444');
    const hi   = shade(base, 15);
    const lo   = shade(base, -10);
    modalBubble.style.display = 'grid';
    modalBubble.style.background = `radial-gradient(60% 60% at 50% 40%, ${hi}, ${lo})`;

    // ✅ COR DO TEXTO DENTRO DA BOLHA (mesma cor do segmento ou contraste)
    const textColor = getContrastColor(base);
    modalQtyBig.style.color = textColor;
    modalUnit.style.color = textColor;
    modalGameInBubble.style.color = textColor;

    // jogo / url
    const game = (p.game || p.game_name || '').trim();
    const url  = normalizeUrl(p.url || p.game_url || '');
    if (game){
      modalGame.style.display = 'block';
      modalGame.innerHTML = 'Jogo: <strong>'+escHtml(game)+'</strong>';
      // opcionalmente aparece dentro da bolha também
      modalGameInBubble.style.display = 'block';
      modalGameInBubble.textContent = game;
    }

    if (url){
      modalCTA.textContent = 'Ir para o jogo';
      modalCTA.onclick = () => window.open(url, '_blank', 'noopener');
    } else {
      modalCTA.textContent = 'Fechar';
      modalCTA.onclick = closeModal;
    }

    openModal();
    try{ ganhou.currentTime = 0; ganhou.play(); }catch(_){}
    ackResult();
    window.__forcedStep = '';
    return;
  }

  // --------- FLUXO DE DERROTA (sem pattern L) ---------
  document.getElementById('modalTitle').textContent  = 'Ah, que pena…';
  modalDetail.textContent = 'Não foi dessa vez. Tente novamente!';
  modalBubble.style.display = 'none';
  modalCTA.textContent = 'Fechar';
  modalCTA.onclick = closeModal;

  openModal();
  try{ perdeu.currentTime = 0; perdeu.play(); }catch(_){}
  ackResult();
  window.__forcedStep = '';
}

/* ===== ✅ LOOP DE ANIMAÇÃO MELHORADO ===== */
function animate(){
  const now=performance.now()/1000; const dt=lastT?Math.min(0.033, now-lastT):1/60; lastT=now;
  
  if(phase==='accelerate'){
    // ✅ ACELERAÇÃO MAIS SUAVE
    speed=Math.min(MAX_SPEED, speed+ACCEL*dt); 
    currentAngle+=speed*dt; 
    anglePath+=speed*dt; 
    maybeTick(currentAngle);
    
    diagClock+=dt; 
    if(diagClock>=0.5){ 
      console.log('[SPIN] phase=accelerate spins=%s speed=%s rpm=%s', 
        (anglePath/TAU).toFixed(2), speed.toFixed(2), (speed/TAU*60).toFixed(1)); 
      diagClock=0; 
    }
    
    if(speed>=MAX_SPEED-1e-3){ 
      phase='cruise'; 
      cruiseClock=0; 
      postAudioClock=0; 
    }
  }
  else if(phase==='cruise'){
    cruiseClock+=dt; 
    if(audioEnded) postAudioClock+=dt;
    currentAngle+=speed*dt; 
    anglePath+=speed*dt; 
    maybeTick(currentAngle);
    
    diagClock+=dt; 
    if(diagClock>=0.5){ 
      console.log('[SPIN] phase=cruise spins=%s speed=%s rpm=%s', 
        (anglePath/TAU).toFixed(2), speed.toFixed(2), (speed/TAU*60).toFixed(1)); 
      diagClock=0; 
    }

    const timeOk = (audioEnded && postAudioClock>=POST_AUDIO_CRUISE_T && cruiseClock>=MIN_TOTAL_CRUISE_T) || (cruiseClock>=MAX_WAIT_FOR_AUDIO);
    if (timeOk){
      // ✅ PRIMEIRO: Espera pelo resultado do servidor com timeout
      if (spinIndex == null){
        resultTimeoutClock += dt;
        if (resultTimeoutClock >= MAX_WAIT_RESULT){
          // Timeout: usa fallback MAS ainda espera pela resposta se chegar depois
          spinIndex = fallbackWeightedPick();
          console.warn('[SPIN] Server timeout, using fallback index:', spinIndex);
        }
      }
      
      // ✅ SEGUNDO: Só inicia desaceleração quando temos um índice válido
      if (spinIndex != null){
        const doneSpins = anglePath / TAU;
        if (doneSpins + DECEL_LEAD_SPINS < SPINS_PROMISED){
          // Continue em cruzeiro até cumprir voltas prometidas
        } else {
          planDecelExact(); 
          phase='decelerate';
        }
      }
    }
  }
  else if(phase==='decelerate'){
    decelClock+=dt; 
    
    // ✅ DESACELERAÇÃO MAIS SUAVE COM EASING
    const progress = Math.min(1, decelClock / decelTime);
    const easeOut = 1 - Math.pow(1 - progress, 1.5); // Easing mais suave
    
    speed = Math.max(0, v0 + decelAccel * decelClock * easeOut);
    currentAngle += speed * dt; 
    anglePath += Math.max(0, speed * dt); 
    maybeTick(currentAngle);
    
    diagClock+=dt; 
    if(diagClock>=0.5){ 
      console.log('[SPIN] phase=decelerate spins=%s speed=%s rpm=%s progress=%.1f%%', 
        (anglePath/TAU).toFixed(2), speed.toFixed(2), (speed/TAU*60).toFixed(1),
        progress * 100
      ); 
      diagClock=0; 
    }
    
    if((currentAngle - decelStartAngle) >= decelDistance - 1e-4 || decelClock>=decelTime){
      currentAngle = decelTargetAngle; 
      speed=0; 
      ensureAlignedThenStop();
    }
  }
  else if(phase==='micro-correct'){
    microClock += dt;
    
    // ✅ CORREÇÃO: Easing ultra suave e natural
    const t = Math.min(1, microClock / MICRO_TIME);
    
    // Easing out quint - muito suave
    const ease = 1 - Math.pow(1 - t, 5);
    
    // interpola do ângulo atual (ao entrar) até o alvo
    if (!animate.__microStart){ animate.__microStart = currentAngle; }
    const start = animate.__microStart;
    currentAngle = start + shortestDelta(start, microTargetAngle) * ease;
    maybeTick(currentAngle);
    
    if (t >= 1){
      animate.__microStart = null;
      phase='idle'; 
      stopSpin();
    }
  }
  
  drawWheel((currentAngle%TAU+TAU)%TAU); 
  requestAnimationFrame(animate);
}

// ✅ CORREÇÃO: Variável v0 precisa ser acessível
let v0 = 0;

function planDecelExact(){
  const targetMid = sectorAngleMid(spinIndex);
  const finalLocal = robustNormalize(-targetMid + POINTER_OFFSET);
  const curLocal = robustNormalize(currentAngle);

  let delta = finalLocal - curLocal;
  if (delta < -Math.PI) delta += TAU;
  if (delta > Math.PI) delta -= TAU;
  if (delta < 0) delta += TAU;

  const doneSpins = Math.max(0, anglePath / TAU);
  let extraTurns  = Math.max(0, SPINS_PROMISED - doneSpins);

  v0 = Math.max(MAX_SPEED * 0.35, Math.min(speed, MAX_SPEED * 0.75));

  let D = delta + extraTurns * TAU;

  let turnsAdded = 0;
  const needsTrail = () => (D < MIN_DECEL_TURNS * TAU);
  const needsTime  = () => ((2 * D / v0) < MIN_DECEL_TIME);

  while ((needsTrail() || needsTime()) && turnsAdded < MAX_EXTRA_TURNS) {
    D += TAU;
    turnsAdded++;
  }

  const T = Math.max(MIN_DECEL_TIME, 2.2 * D / v0);

  decelStartAngle = currentAngle;
  decelTargetAngle = currentAngle + D - TARGET_SAFETY;
  decelDistance = D;
  decelTime = T;
  decelClock = 0;
  decelAccel = -v0 / T;

  console.log('[SPIN] planDecelExact - targetMid=%s°, delta=%s°, D=%s°, T=%ss, v0=%.2f, turnsAdded=%s',
    (targetMid * 180/Math.PI).toFixed(1),
    (delta * 180/Math.PI).toFixed(1),
    (D * 180/Math.PI).toFixed(1),
    T.toFixed(2),
    v0,
    turnsAdded
  );
}

function drawAll(){ fitWheelToViewport(); resizeCanvas(wheel); resizeCanvas(fx); resizeCanvas(fxBG); drawWheel(currentAngle); drawLeds(); drawFxBG(); drawFxModal(); }
function fxLoop(){ drawLeds(); drawFxBG(); drawFxModal(); requestAnimationFrame(fxLoop); }

/* ===== interações ===== */
function tryStart(e){
  e.preventDefault();
  
  if (spinCooldown) {
    showWarningMessage();
    return;
  }
  
  if(!muted && bgm.paused){ try{ bgm.play(); }catch{} }
  if(phase==='idle') {
    startSpin();
  }
}
wheelbox.addEventListener('click', tryStart);
wheelbox.addEventListener('touchstart', tryStart, {passive:false});

/* aplicar pointer (cor/posição) */
function applyPointerColor(hex){
  const base = parseHex(hex).hex;
  const hi   = shade(base, 18);
  const mid  = base;
  const lo   = shade(base, -24);
  const rim  = shade(base, -38);
  pointer.style.setProperty('--pc', base);
  pointer.style.setProperty('--pc-hi', hi);
  pointer.style.setProperty('--pc-mid', mid);
  pointer.style.setProperty('--pc-lo', lo);
  pointer.style.setProperty('--pc-rim', rim);
}
function applyPointerPosition(pos){
  const p = String(pos||'right').toLowerCase();
  pointer.classList.remove('pos-top','pos-right','pos-bottom','pos-left');
  pointer.classList.add(
    p==='top' ? 'pos-top' :
    p==='bottom' ? 'pos-bottom' :
    p==='left' ? 'pos-left' : 'pos-right'
  );
  POINTER_OFFSET = getPointerOffset(p);
}

/* start */
addEventListener('resize', fitWheelToViewport);
titleEl.textContent = document.title || 'Roleta';
fitWheelToViewport(); drawAll();
applyPointerColor(POINTER_COLOR);
applyPointerPosition(POINTER_POSITION);
requestAnimationFrame(animate); requestAnimationFrame(fxLoop);

// ===== DEBUG EXTRA =====
(function() {
  const originalFetch = window.fetch;
  window.fetch = async function(...args) {
    const response = await originalFetch.apply(this, args);
    const clone = response.clone();
    if (args[0] && (String(args[0]).includes('/api/spin.php') || String(args[0]).includes('/api/spin_ack.php'))) {
      clone.json().then(data => {
        console.group('🎰 ROULETTE DEBUG - API Response');
        console.log('URL:', args[0]);
        console.log('Method:', args[1]?.method || 'GET');
        console.log('Data:', data);
        if (data && data.debug) {
          console.group('🔍 Detailed Debug Info');
          console.log('Pattern:', data.debug.pattern || 'none');
          console.log('Step:', data.debug.step || 'none');
          console.log('ACK Count:', data.debug.ack_count || 0);
          if (data.debug.segments) {
            console.group('📊 Segments Analysis');
            data.debug.segments.forEach((seg, index) => {
              console.log(`Segment ${index}:`, seg);
            });
            console.groupEnd();
          }
          if (data.debug.chosen_details) {
            console.group('✅ Chosen Segment');
            console.log('ID:', data.debug.chosen_details.id);
            console.log('Index:', data.debug.chosen_details.index);
            console.log('Type:', data.debug.chosen_details.type);
            console.log('Weight:', data.debug.chosen_details.weight);
            console.log('Effective Weight:', data.debug.chosen_details.effective_weight);
            console.log('Forced:', data.debug.chosen_details.forced);
            console.groupEnd();
          }
          console.groupEnd();
        }
        console.groupEnd();
      }).catch(() => {});
    }
    return response;
  };

  const originalStartSpin = window.startSpin;
  window.startSpin = function() {
    console.group('🎯 ROULETTE SPIN STARTED');
    console.log('Timestamp:', new Date().toISOString());
    console.log('Current Phase:', typeof phase !== 'undefined' ? phase : 'undefined');
    console.groupEnd();
    return originalStartSpin.apply(this, arguments);
  };

  const originalStopSpin = window.stopSpin;
  window.stopSpin = function() {
    console.group('🛑 ROULETTE SPIN STOPPED');
    console.log('Timestamp:', new Date().toISOString());
    console.log('Final Angle:', currentAngle);
    console.log('Spin Index:', spinIndex);
    if (PRIZES[spinIndex]) {
      console.log('Prize:', PRIZES[spinIndex]);
      console.log('Prize Weight:', PRIZES[spinIndex].weight);
      console.log('Effective Weight:', (PRIZES[spinIndex].weight <= 1) ? 0 : PRIZES[spinIndex].weight);
      console.log('Hide Text:', PRIZES[spinIndex].hide_text_all);
    }
    console.groupEnd();
    return originalStopSpin.apply(this, arguments);
  };

  console.group('🎰 ROULETTE INITIALIZATION');
  console.log('Total Segments:', PRIZES.length);
  console.log('Segment Weights:');
  PRIZES.forEach((prize, index) => {
    console.log(`  ${index}: ${prize.weight} (effective: ${prize.weight <= 1 ? 0 : prize.weight}) - ${prize.primary} ${prize.unit} - HideText: ${prize.hide_text_all}`);
  });
  console.groupEnd();
})();

// ✅ CORREÇÃO DE SINCRONIZAÇÃO - Garantir que frontend e backend usam a mesma lógica
function calculateSegmentIndex(angle, pointerOffset, slice, numSegments) {
    const normalized = ((pointerOffset - angle) % TAU + TAU) % TAU;
    return Math.floor(normalized / slice) % numSegments;
}

// ✅ OVERRIDE da função de normalização para garantir consistência
window.robustNormalize = function(angle) {
    angle = angle % TAU;
    if (angle < 0) angle += TAU;
    return angle;
};

console.log('✅ Sincronização frontend/backend carregada');
</script>

<script>
// ✅ CORREÇÃO DE SINCRONIZAÇÃO - Garantir que frontend e backend usem a mesma lógica
function calculateSegmentIndex(angle, pointerOffset, slice, numSegments) {
    const normalized = ((pointerOffset - angle) % TAU + TAU) % TAU;
    return Math.floor(normalized / slice) % numSegments;
}

// ✅ OVERRIDE da função de normalização para garantir consistência
window.robustNormalize = function(angle) {
    angle = angle % TAU;
    if (angle < 0) angle += TAU;
    return angle;
};

console.log('✅ Sincronização frontend/backend carregada');
</script></body>
</html>