/**
* Script standalone para criação dinâmica do jogo de roleta
* Pode ser usado via JWT ou função global window.sellflux_game()
*/
(function() {
'use strict';
// Lista de países com máscaras e códigos de discagem
const countryList = [
{
country_code: "AD",
phone_mask: "999-999",
country_name: "Andorra",
regionCode: "376",
emoji: "🇦🇩",
},
{
country_code: "AE",
phone_mask: "(99) 999-9999",
country_name: "Emirados Árabes Unidos",
regionCode: "971",
emoji: "🇦🇪",
},
{
country_code: "AF",
phone_mask: "(99) 999-9999",
country_name: "Afeganistão",
regionCode: "93",
emoji: "🇦🇫",
},
{
country_code: "AL",
phone_mask: "(99) 999-9999",
country_name: "Albânia",
regionCode: "355",
emoji: "🇦🇱",
},
{
country_code: "AM",
phone_mask: "(99) 999-9999",
country_name: "Armênia",
regionCode: "374",
emoji: "🇦🇲",
},
{
country_code: "AO",
phone_mask: "(99) 999-9999",
country_name: "Angola",
regionCode: "244",
emoji: "🇦🇴",
},
{
country_code: "AR",
phone_mask: "(99) 9999-9999",
country_name: "Argentina",
regionCode: "54",
emoji: "🇦🇷",
},
{
country_code: "AT",
phone_mask: "(9) 9999-9999",
country_name: "Áustria",
regionCode: "43",
emoji: "🇦🇹",
},
{
country_code: "AU",
phone_mask: "(99) 9999-9999",
country_name: "Austrália",
regionCode: "61",
emoji: "🇦🇺",
},
{
country_code: "AZ",
phone_mask: "(99) 999-9999",
country_name: "Azerbaijão",
regionCode: "994",
emoji: "🇦🇿",
},
{
country_code: "BA",
phone_mask: "(99) 999-999",
country_name: "Bósnia e Herzegovina",
regionCode: "387",
emoji: "🇧🇦",
},
{
country_code: "BD",
phone_mask: "(99) 9999-9999",
country_name: "Bangladesh",
regionCode: "880",
emoji: "🇧🇩",
},
{
country_code: "BE",
phone_mask: "(99) 999-99-99",
country_name: "Bélgica",
regionCode: "32",
emoji: "🇧🇪",
},
{
country_code: "BG",
phone_mask: "(999) 999-999",
country_name: "Bulgária",
regionCode: "359",
emoji: "🇧🇬",
},
{
country_code: "BH",
phone_mask: "9999-9999",
country_name: "Butão",
regionCode: "975",
emoji: "🇧🇹",
},
{
country_code: "BJ",
phone_mask: "9999-99999",
country_name: "Benim",
regionCode: "229",
emoji: "🇧🇯",
},
{
country_code: "BO",
phone_mask: "(9) 999-9999",
country_name: "Bolívia",
regionCode: "591",
emoji: "🇧🇴",
},
{
country_code: "BR",
phone_mask: "(99) 99999-9999",
country_name: "Brasil",
regionCode: "55",
selected: true,
emoji: "🇧🇷",
},
{
country_code: "BY",
phone_mask: "(99) 999-99-99",
country_name: "Belarus",
regionCode: "375",
emoji: "🇧🇾",
},
{
country_code: "CA",
phone_mask: "(999) 999-9999",
country_name: "Canadá",
regionCode: "1",
emoji: "🇨🇦",
},
{
country_code: "CD",
phone_mask: "(99) 999-9999",
country_name: "República Democrática do Congo",
regionCode: "243",
emoji: "🇨🇩",
},
{
country_code: "CH",
phone_mask: "(99) 999-99-99",
country_name: "Suíça",
regionCode: "41",
emoji: "🇨🇭",
},
{
country_code: "CI",
phone_mask: "(99) 99-99-99",
country_name: "Costa do Marfim",
regionCode: "225",
emoji: "🇨🇮",
},
{
country_code: "CL",
phone_mask: "(99) 9999-9999",
country_name: "Chile",
regionCode: "56",
emoji: "🇨🇱",
},
{
country_code: "CM",
phone_mask: "(99) 9999-9999",
country_name: "Camarões",
regionCode: "237",
emoji: "🇨🇲",
},
{
country_code: "CO",
phone_mask: "(9) 999-9999",
country_name: "Colômbia",
regionCode: "57",
emoji: "🇨🇴",
},
{
country_code: "CV",
phone_mask: "(99) 999-9999",
country_name: "Cabo Verde",
regionCode: "238",
emoji: "🇨🇻",
},
{
country_code: "CY",
phone_mask: "9999-9999",
country_name: "Chipre",
regionCode: "357",
emoji: "🇨🇾",
},
{
country_code: "CZ",
phone_mask: "999-999-999",
country_name: "República Tcheca",
regionCode: "420",
emoji: "🇨🇿",
},
{
country_code: "DE",
phone_mask: "(99) 9999-9999",
country_name: "Alemanha",
regionCode: "49",
emoji: "🇩🇪",
},
{
country_code: "DK",
phone_mask: "(99) 9999-9999",
country_name: "Dinamarca",
regionCode: "45",
emoji: "🇩🇰",
},
{
country_code: "DO",
phone_mask: "(999) 999-9999",
country_name: "República Dominicana",
regionCode: "1",
emoji: "🇩🇴",
},
{
country_code: "EC",
phone_mask: "(99) 999-9999",
country_name: "Equador",
regionCode: "593",
emoji: "🇪🇨",
},
{
country_code: "EE",
phone_mask: "(99) 9999-9999",
country_name: "Estônia",
regionCode: "372",
emoji: "🇪🇪",
},
{
country_code: "EG",
phone_mask: "(99) 9999-9999",
country_name: "Egito",
regionCode: "20",
emoji: "🇪🇬",
},
{
country_code: "ES",
phone_mask: "999-999-999",
country_name: "Espanha",
regionCode: "34",
emoji: "🇪🇸",
},
{
country_code: "ET",
phone_mask: "999-999-999",
country_name: "Etiópia",
regionCode: "251",
emoji: "🇪🇹",
},
{
country_code: "FI",
phone_mask: "999-999-999",
country_name: "Finlândia",
regionCode: "358",
emoji: "🇫🇮",
},
{
country_code: "FO",
phone_mask: "999999",
country_name: "Ilhas Faroé",
regionCode: "298",
emoji: "🇫🇴",
},
{
country_code: "FR",
phone_mask: "(99) 9999-9999",
country_name: "França",
regionCode: "33",
emoji: "🇫🇷",
},
{
country_code: "GB",
phone_mask: "(99) 9999-9999",
country_name: "Reino Unido",
regionCode: "44",
emoji: "🇬🇧",
},
{
country_code: "GE",
phone_mask: "(99) 9999-9999",
country_name: "Geórgia",
regionCode: "995",
emoji: "🇬🇪",
},
{
country_code: "GH",
phone_mask: "(99) 9999-9999",
country_name: "Gana",
regionCode: "233",
emoji: "🇬🇭",
},
{
country_code: "GI",
phone_mask: "99999",
country_name: "Gibraltar",
regionCode: "350",
emoji: "🇬🇮",
},
{
country_code: "GL",
phone_mask: "99999",
country_name: "Groenlândia",
regionCode: "299",
emoji: "🇬🇱",
},
{
country_code: "GR",
phone_mask: "9999-999999",
country_name: "Grécia",
regionCode: "30",
emoji: "🇬🇷",
},
{
country_code: "HR",
phone_mask: "(99) 999-9999",
country_name: "Croácia",
regionCode: "385",
emoji: "🇭🇷",
},
{
country_code: "HU",
phone_mask: "(99) 999-9999",
country_name: "Hungria",
regionCode: "36",
emoji: "🇭🇺",
},
{
country_code: "ID",
phone_mask: "(99) 9999-9999",
country_name: "Indonésia",
regionCode: "62",
emoji: "🇮🇩",
},
{
country_code: "IE",
phone_mask: "(99) 999-9999",
country_name: "Irlanda",
regionCode: "353",
emoji: "🇮🇪",
},
{
country_code: "IL",
phone_mask: "99-999-9999",
country_name: "Israel",
regionCode: "972",
emoji: "🇮🇱",
},
{
country_code: "IM",
phone_mask: "(99) 9999-9999",
country_name: "Ilha de Man",
regionCode: "44",
emoji: "🇮🇲",
},
{
country_code: "IN",
phone_mask: "(99) 9999-9999",
country_name: "Índia",
regionCode: "91",
emoji: "🇮🇳",
},
{
country_code: "IQ",
phone_mask: "(99) 9999-9999",
country_name: "Iraque",
regionCode: "964",
emoji: "🇮🇶",
},
{
country_code: "IR",
phone_mask: "(9999) 9999-9999",
country_name: "Irã",
regionCode: "98",
emoji: "🇮🇷",
},
{
country_code: "IS",
phone_mask: "999-9999",
country_name: "Islândia",
regionCode: "354",
emoji: "🇮🇸",
},
{
country_code: "IT",
phone_mask: "(99) 9999-9999",
country_name: "Itália",
regionCode: "39",
emoji: "🇮🇹",
},
{
country_code: "JP",
phone_mask: "(99) 9999-9999",
country_name: "Japão",
regionCode: "81",
emoji: "🇯🇵",
},
{
country_code: "JO",
phone_mask: "(99) 999-9999",
country_name: "Jordânia",
regionCode: "962",
emoji: "🇯🇴",
},
{
country_code: "KE",
phone_mask: "(99) 9999-999",
country_name: "Quênia",
regionCode: "254",
emoji: "🇰🇪",
},
{
country_code: "KR",
phone_mask: "(99) 999-9999",
country_name: "Coreia do Sul",
regionCode: "82",
emoji: "🇰🇷",
},
{
country_code: "KZ",
phone_mask: "(999) 999-9999",
country_name: "Cazaquistão",
regionCode: "7",
emoji: "🇰🇿",
},
{
country_code: "LB",
phone_mask: "(99) 999-999",
country_name: "Líbano",
regionCode: "961",
emoji: "🇱🇧",
},
{
country_code: "LI",
phone_mask: "999 9999",
country_name: "Liechtenstein",
regionCode: "423",
emoji: "🇱🇮",
},
{
country_code: "LT",
phone_mask: "(999) 99999",
country_name: "Lituânia",
regionCode: "370",
emoji: "🇱🇹",
},
{
country_code: "LU",
phone_mask: "999-999",
country_name: "Luxemburgo",
regionCode: "352",
emoji: "🇱🇺",
},
{
country_code: "LV",
phone_mask: "9999-9999",
country_name: "Letônia",
regionCode: "371",
emoji: "🇱🇻",
},
{
country_code: "MA",
phone_mask: "9999-99999",
country_name: "Marrocos",
regionCode: "212",
emoji: "🇲🇦",
},
{
country_code: "MC",
phone_mask: "99-99-99-99",
country_name: "Mônaco",
regionCode: "377",
emoji: "🇲🇨",
},
{
country_code: "MD",
phone_mask: "999-9999",
country_name: "Moldávia",
regionCode: "373",
emoji: "🇲🇩",
},
{
country_code: "ME",
phone_mask: "99-99-9999",
country_name: "Montenegro",
regionCode: "382",
emoji: "🇲🇪",
},
{
country_code: "MK",
phone_mask: "99-999-999",
country_name: "Macedônia do Norte",
regionCode: "389",
emoji: "🇲🇰",
},
{
country_code: "MM",
phone_mask: "9999-9999",
country_name: "Mianmar",
regionCode: "95",
emoji: "🇲🇲",
},
{
country_code: "MT",
phone_mask: "9999-9999",
country_name: "Malta",
regionCode: "356",
emoji: "🇲🇹",
},
{
country_code: "MX",
phone_mask: "(999) 999-9999",
country_name: "México",
regionCode: "52",
emoji: "🇲🇽",
},
{
country_code: "MY",
phone_mask: "9999-9999",
country_name: "Malásia",
regionCode: "60",
emoji: "🇲🇾",
},
{
country_code: "MZ",
phone_mask: "99-999-9999",
country_name: "Moçambique",
regionCode: "258",
emoji: "🇲🇿",
},
{
country_code: "NG",
phone_mask: "9999-9999",
country_name: "Nigéria",
regionCode: "234",
emoji: "🇳🇬",
},
{
country_code: "NL",
phone_mask: "(99) 999-9999",
country_name: "Países Baixos",
regionCode: "31",
emoji: "🇳🇱",
},
{
country_code: "NO",
phone_mask: "999-999-99",
country_name: "Noruega",
regionCode: "47",
emoji: "🇳🇴",
},
{
country_code: "NP",
phone_mask: "99-999-999",
country_name: "Nepal",
regionCode: "977",
emoji: "🇳🇵",
},
{
country_code: "NZ",
phone_mask: "9-9999-9999",
country_name: "Nova Zelândia",
regionCode: "64",
emoji: "🇳🇿",
},
{
country_code: "PA",
phone_mask: "9999-9999",
country_name: "Panamá",
regionCode: "507",
emoji: "🇵🇦",
},
{
country_code: "PE",
phone_mask: "999-999999",
country_name: "Peru",
regionCode: "51",
emoji: "🇵🇪",
},
{
country_code: "PH",
phone_mask: "(99) 9999-9999",
country_name: "Filipinas",
regionCode: "63",
emoji: "🇵🇭",
},
{
country_code: "PK",
phone_mask: "(999) 9999999",
country_name: "Paquistão",
regionCode: "92",
emoji: "🇵🇰",
},
{
country_code: "PL",
phone_mask: "999-999-999",
country_name: "Polônia",
regionCode: "48",
emoji: "🇵🇱",
},
{
country_code: "PT",
phone_mask: "999-999-999",
country_name: "Portugal",
regionCode: "351",
emoji: "🇵🇹",
},
{
country_code: "PY",
phone_mask: "(99) 999-9999",
country_name: "Paraguai",
regionCode: "595",
emoji: "🇵🇾",
},
{
country_code: "RO",
phone_mask: "(99) 999-9999",
country_name: "Romênia",
regionCode: "40",
emoji: "🇷🇴",
},
{
country_code: "RS",
phone_mask: "(99) 999-9999",
country_name: "Sérvia",
regionCode: "381",
emoji: "🇷🇸",
},
{
country_code: "RU",
phone_mask: "999-999-99-99",
country_name: "Rússia",
regionCode: "7",
emoji: "🇷🇺",
},
{
country_code: "SA",
phone_mask: "999-999-9999",
country_name: "Arábia Saudita",
regionCode: "966",
emoji: "🇸🇦",
},
{
country_code: "SD",
phone_mask: "99999999",
country_name: "Sudão",
regionCode: "249",
emoji: "🇸🇩",
},
{
country_code: "SE",
phone_mask: "99-999-99-99",
country_name: "Suécia",
regionCode: "46",
emoji: "🇸🇪",
},
{
country_code: "SG",
phone_mask: "9999-9999",
country_name: "Singapura",
regionCode: "65",
emoji: "🇸🇬",
},
{
country_code: "SI",
phone_mask: "99-999-999",
country_name: "Eslovênia",
regionCode: "386",
emoji: "🇸🇮",
},
{
country_code: "SK",
phone_mask: "99-9999-999",
country_name: "Eslováquia",
regionCode: "421",
emoji: "🇸🇰",
},
{
country_code: "SM",
phone_mask: "999-999-999",
country_name: "San Marino",
regionCode: "378",
emoji: "🇸🇲",
},
{
country_code: "SO",
phone_mask: "99999999",
country_name: "Somália",
regionCode: "252",
emoji: "🇸🇴",
},
{
country_code: "SY",
phone_mask: "9999-9999",
country_name: "Síria",
regionCode: "963",
emoji: "🇸🇾",
},
{
country_code: "TH",
phone_mask: "(99) 999-9999",
country_name: "Tailândia",
regionCode: "66",
emoji: "🇹🇭",
},
{
country_code: "TN",
phone_mask: "(99) 999-999",
country_name: "Tunísia",
regionCode: "216",
emoji: "🇹🇳",
},
{
country_code: "TR",
phone_mask: "(999) 999-9999",
country_name: "Turquia",
regionCode: "90",
emoji: "🇹🇷",
},
{
country_code: "TZ",
phone_mask: "999-999-999",
country_name: "Tanzânia",
regionCode: "255",
emoji: "🇹🇿",
},
{
country_code: "UA",
phone_mask: "99-999-99-99",
country_name: "Ucrânia",
regionCode: "380",
emoji: "🇺🇦",
},
{
country_code: "UG",
phone_mask: "999-999-999",
country_name: "Uganda",
regionCode: "256",
emoji: "🇺🇬",
},
{
country_code: "US",
phone_mask: "(999) 999-9999",
country_name: "Estados Unidos",
regionCode: "1",
emoji: "🇺🇸",
},
{
country_code: "UY",
phone_mask: "9999-9999",
country_name: "Uruguai",
regionCode: "598",
emoji: "🇺🇾",
},
{
country_code: "VA",
phone_mask: "(9999) 9999-9999",
country_name: "Cidade do Vaticano",
regionCode: "39",
emoji: "🇻🇦",
},
{
country_code: "VE",
phone_mask: "(99) 999-9999",
country_name: "Venezuela",
regionCode: "58",
emoji: "🇻🇪",
},
{
country_code: "VN",
phone_mask: "(99) 9999-9999",
country_name: "Vietnã",
regionCode: "84",
emoji: "🇻🇳",
},
{
country_code: "XK",
phone_mask: "(99) 999-999",
country_name: "Kosovo",
regionCode: "383",
emoji: "🇽🇰",
},
{
country_code: "ZM",
phone_mask: "(99) 999-9999",
country_name: "Zâmbia",
regionCode: "260",
emoji: "🇿🇲",
},
{
country_code: "ZW",
phone_mask: "(99) 999-9999",
country_name: "Zimbábue",
regionCode: "263",
emoji: "🇿🇼",
},
];
// Função principal que inicializa o jogo
function initRouletteGame(config) {
// Marca que estamos na versão full para aplicar estilos específicos
document.body.classList.add('sfx_sellflux-full');
// Se config não for passado, tenta obter via JWT dos parâmetros
if (!config) {
config = getConfigFromJWT();
if (!config) return;
}
// Validar configuração
if (!config.settings) {
console.error('Configuração inválida para o jogo - settings é obrigatório');
return;
}
// SUBSTITUIÇÃO: uso da nova função reutilizável
renderGameContent(document.body, config);
setupGameLogic(config);
}
// ==== RENDERIZAÇÃO DO JOGO COM SUPORTE A SHADOW DOM ====
function renderGameContent(parentElement, config, styleTarget = document.head) {
// Aplica estilos e injeta o HTML principal do jogo no contêiner desejado
injectStyles(config.settings, styleTarget);
const gameContainer = createGameHTML(config);
parentElement.appendChild(gameContainer);
// Carregar possíveis dados salvos do usuário
loadSavedUserData();
}
// Função para gerenciar cookies do usuário
function manageCookies() {
return {
// Salvar dados do usuário nos cookies
saveUserData: function(userData) {
const expires = new Date();
expires.setTime(expires.getTime() + (365 * 24 * 60 * 60 * 1000)); // 1 ano
const cookieData = {
name: userData.name || '',
email: userData.email || '',
phone: userData.phone || '',
phoneWithDdi: userData.phoneWithDdi || '',
countryCode: userData.countryCode || '55'
};
document.cookie = `sellflux_user_data=${JSON.stringify(cookieData)}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`;
},
// Carregar dados do usuário dos cookies
loadUserData: function() {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [name, value] = cookie.trim().split('=');
if (name === 'sellflux_user_data') {
try {
return JSON.parse(decodeURIComponent(value));
} catch (e) {
console.warn('Erro ao carregar dados do cookie:', e);
return null;
}
}
}
return null;
},
// Salvar timestamp da primeira participação
saveFirstParticipationTimestamp: function() {
const expires = new Date();
expires.setTime(expires.getTime() + (365 * 24 * 60 * 60 * 1000)); // 1 ano
const timestamp = new Date().getTime();
document.cookie = `sellflux_first_participation=${timestamp}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`;
},
// Carregar timestamp da primeira participação
loadFirstParticipationTimestamp: function() {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [name, value] = cookie.trim().split('=');
if (name === 'sellflux_first_participation') {
try {
return parseInt(value);
} catch (e) {
console.warn('Erro ao carregar timestamp do cookie:', e);
return null;
}
}
}
return null;
},
// Verificar se pode mostrar o modal novamente baseado na frequência
canShowModalAgain: function(reappearanceFrequencyHours) {
const firstParticipation = this.loadFirstParticipationTimestamp();
// Se não há registro, é a primeira vez
if (!firstParticipation) {
return true;
}
// Se frequência é 0, sempre mostrar
if (reappearanceFrequencyHours === 0) {
return true;
}
// Calcular diferença em horas
const now = new Date().getTime();
const diffInHours = (now - firstParticipation) / (1000 * 60 * 60);
return diffInHours >= reappearanceFrequencyHours;
},
// Limpar dados dos cookies
clearUserData: function() {
document.cookie = 'sellflux_user_data=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax';
},
// Limpar timestamp de participação
clearParticipationTimestamp: function() {
document.cookie = 'sellflux_first_participation=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax';
}
};
}
// NOVO: função utilitária para envio dos dados do lead evitando problemas de CORS
// Tenta usar navigator.sendBeacon (não sofre bloqueio de CORS). Caso não haja suporte,
// utiliza XMLHttpRequest com cabeçalho simples (application/json) que não exige preflight.
function sendLeadRequest(url, data, onSuccess, onError) {
let query=window.location.search
if(query){
url=url+query
}
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
body: JSON.stringify(data)
})
.then(response => {
if (response.ok) {
if (onSuccess) onSuccess();
} else {
throw new Error(`Erro HTTP: ${response.status}`);
}
})
.catch(error => {
console.error('Erro na requisição:', error);
if (onError) onError(error);
});
}
/**
* Codifica string para HTML usando entidades numéricas para caracteres não-ASCII e escapa &, <, >, "
* Garante renderização correta de acentos em páginas com encoding não-UTF-8.
*/
function encodeHtml(str){
if (str === undefined || str === null) return '';
let out = '';
for (let i = 0; i < str.length; i++) {
const cp = str.codePointAt(i);
if (cp === undefined) continue;
if (cp > 0xFFFF) i++;
if (cp === 34) out += '"';
else if (cp === 38) out += '&';
else if (cp === 60) out += '<';
else if (cp === 62) out += '>';
else if (cp < 32 || cp > 126) out += '' + cp.toString(16).toUpperCase() + ';';
else out += String.fromCharCode(cp);
}
return out;
}
// Função para carregar dados salvos nos campos do formulário
function loadSavedUserData() {
const cookieManager = manageCookies();
const savedData = cookieManager.loadUserData();
if (savedData) {
// Preencher os campos com dados salvos
setTimeout(() => {
const nameField = document.getElementById('name');
const emailField = document.getElementById('email');
const phoneField = document.getElementById('phone');
const countrySelect = document.getElementById('country-select');
if (nameField && savedData.name) nameField.value = savedData.name;
if (emailField && savedData.email) emailField.value = savedData.email;
// CORREÇÃO: Primeiro definir o país e depois o telefone
if (countrySelect && savedData.countryCode) {
countrySelect.value = savedData.countryCode;
}
// CORREÇÃO: Para o telefone, verificar se há um número formatado salvo (sem DDI)
if (phoneField && savedData.phone) {
// Se o phone salvo tem DDI (+55...), extrair apenas os números após o DDI
let phoneNumber = savedData.phone;
if (phoneNumber.startsWith('+')) {
// Remove o DDI (ex: +5511999999999 -> 11999999999)
const countryCode = savedData.countryCode || '55';
const ddiRegex = new RegExp('^\\+' + countryCode);
phoneNumber = phoneNumber.replace(ddiRegex, '');
}
// Garantir que é apenas números
phoneNumber = phoneNumber.replace(/\D/g, '');
phoneField.value = phoneNumber;
// Aplicar máscara após definir o valor
if (countrySelect) {
countrySelect.dispatchEvent(new Event('change'));
}
}
// Adicionar feedback visual para campos preenchidos
[nameField, emailField, phoneField].forEach(field => {
if (field && field.value) {
field.classList.add('sfx_field-prefilled');
}
});
}, 100);
}
}
// Função para obter configuração via JWT (método original)
function getConfigFromJWT() {
// Decodifica JWT sem biblioteca externa
function parseJwt(token) {
try {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
} catch (e) {
console.error('Erro ao decodificar JWT:', e);
return null;
}
}
// Obtém parâmetros da URL
function getUrlParams() {
// Tentar pegar da query string primeiro
let searchParams = window.location.search;
// Se não houver, tentar pegar após o hash (para iframe com blob URL)
if (!searchParams && window.location.hash) {
const hashParts = window.location.hash.split('?');
if (hashParts.length > 1) {
searchParams = '?' + hashParts[1];
}
}
const params = new URLSearchParams(searchParams);
const token = params.get('token');
const apiUrl = params.get('apiUrl') || '/api/lead';
if (!token) {
console.error('Token JWT não fornecido');
return null;
}
const decoded = parseJwt(token);
if (!decoded) return null;
return {
settings: decoded.settings || {},
prizes: decoded.prizes || [],
apiUrl: apiUrl
};
}
return getUrlParams();
}
// ==== INICIALIZA JOGO DENTRO DO MODAL ISOLADO ====
function initRouletteGameModal(config, shadowRoot) {
// Validar configuração
if (!config.settings) {
console.error('Configuração inválida para o jogo - settings é obrigatório');
return;
}
const modalContent = shadowRoot.getElementById('modal-content');
if (!modalContent) {
console.error('Container do modal não encontrado');
return;
}
// SUBSTITUIÇÃO: uso da nova função reutilizável (estilos no ShadowRoot)
renderGameContent(modalContent, config, shadowRoot);
setupGameLogicModal(config); // A lógica permanece a mesma graças ao patch de DOM
}
window.sellflux_button_cta = function(settings, prizes, apiUrl) {
// ================= CONFIGURAÇÃO BÁSICA =================
const config = {
settings: {
title: 'Gire e Ganhe!',
subtitle: 'Preencha seus dados e gire a roleta para ganhar prêmios incríveis!',
buttonText: 'Girar a Roleta!',
buttonOpenModalText: 'SORTEIO',
logoUrl: '',
bannerUrl: '',
backgroundColor: '#000000',
titleColor: '#ffffff',
buttonColor: '#f56a00',
infoColor: '#ffffff',
borderColor: '#000000',
reappearanceFrequency: 0,
openingDelay: 0,
custom_keys: [],
email_field: true,
phone_field: true,
name_field: true,
showPlayAgain: true,
buttonConfig: {
position: 'right', // 'left' | 'right'
type: 'text', // 'text' | 'image'
text: 'CTA', // texto mostrado quando type = text
imageUrl: '', // url da imagem quando type = image
modalPosition: 'center', // 'center' | 'anchored'
...((settings && settings.buttonConfig) || {})
},
...settings
},
prizes: prizes || [],
apiUrl: apiUrl || '/api/webhook/lead'
};
// ================= CRIAÇÃO DO BOTÃO CTA =================
const btnCfg = config.settings.buttonConfig;
// Evita duplicação
const existing = document.getElementById('sellflux-cta-button');
if (existing) existing.remove();
const ctaWrapper = document.createElement('div');
ctaWrapper.id = 'sellflux-cta-button';
ctaWrapper.style.position = 'fixed';
ctaWrapper.style.bottom = '20px';
ctaWrapper.style[btnCfg.position === 'left' ? 'left' : 'right'] = '20px';
ctaWrapper.style.zIndex = '9999999';
// Configuração baseada no tipo
const isMobile = window.innerWidth <= 768;
if (btnCfg.type === 'text') {
// Botão de texto - largura dinâmica, cantos levemente arredondados
ctaWrapper.style.width = 'auto';
ctaWrapper.style.height = 'auto';
ctaWrapper.style.borderRadius = '5px';
ctaWrapper.style.background = config.settings.buttonColor;
ctaWrapper.style.display = 'inline-block';
ctaWrapper.style.padding = isMobile ? '10px 15px' : '12px 20px';
// Limitar largura em mobile para não ultrapassar a tela
if (isMobile) {
ctaWrapper.style.maxWidth = 'calc(100vw - 60px)'; // 20px margem + 20px segurança de cada lado
ctaWrapper.style.overflow = 'hidden';
}
} else {
// Botão de imagem - circular como WhatsApp (menor em mobile)
const size = isMobile ? '48px' : '56px';
ctaWrapper.style.width = size;
ctaWrapper.style.height = size;
ctaWrapper.style.borderRadius = '50%';
ctaWrapper.style.background = config.settings.buttonColor;
ctaWrapper.style.display = 'flex';
ctaWrapper.style.alignItems = 'center';
ctaWrapper.style.justifyContent = 'center';
}
let innerElem;
if (btnCfg.type === 'image' && btnCfg.imageUrl) {
innerElem = new Image();
innerElem.src = btnCfg.imageUrl;
innerElem.alt = btnCfg.text || 'CTA';
// Tamanho da imagem proporcional ao container
const imageSize = isMobile ? '30px' : '36px';
innerElem.style.width = imageSize;
innerElem.style.height = imageSize;
// innerElem.style.borderRadius = '50%';
innerElem.style.objectFit = 'cover';
innerElem.style.pointerEvents = 'none';
} else {
innerElem = document.createElement('span');
innerElem.textContent = btnCfg.text || config.settings.buttonOpenModalText;
innerElem.style.color = config.settings.infoColor;
innerElem.style.fontSize = isMobile ? '12px' : '14px';
innerElem.style.fontWeight = 'bold';
innerElem.style.whiteSpace = isMobile ? 'normal' : 'nowrap';
innerElem.style.pointerEvents = 'none';
innerElem.style.textAlign = 'center';
innerElem.style.lineHeight = isMobile ? '1.2' : '1';
// Permitir quebra de linha em mobile se necessário
if (isMobile) {
innerElem.style.wordBreak = 'break-word';
innerElem.style.hyphens = 'auto';
}
}
// Sombra sutil
ctaWrapper.style.boxShadow = '0 3px 10px rgba(0,0,0,0.25)';
ctaWrapper.style.transition = 'transform 0.3s ease';
ctaWrapper.addEventListener('mouseenter', () => {
ctaWrapper.style.transform = 'scale(1.05)';
});
ctaWrapper.addEventListener('mouseleave', () => {
ctaWrapper.style.transform = 'scale(1)';
});
// Cursor pointer para ambos os tipos
ctaWrapper.style.cursor = 'pointer';
ctaWrapper.appendChild(innerElem);
document.body.appendChild(ctaWrapper);
// ================= CLICK HANDLER =================
ctaWrapper.addEventListener('click', function() {
// Evitar abrir múltiplos modais
if (document.getElementById('sellflux-modal-overlay')) return;
// Usa a função interna existente para criar e mostrar o modal
const isAnchored = btnCfg.modalPosition === 'anchored';
const shadow = showModalDirectly(config, isAnchored);
// Ajusta posicionamento se solicitado
if (isAnchored && shadow) {
const container = shadow.getElementById('sellflux-modal-container');
if (container) {
// Posiciona o modal acima do botão CTA
const ctaRect = ctaWrapper.getBoundingClientRect();
const bottomOffset = window.innerHeight - ctaRect.top + 8; // 8px de margem
const modalIsMobile = window.innerWidth <= 768;
container.style.position = 'fixed';
container.style.bottom = bottomOffset + 'px';
// Ajustes para mobile
if (modalIsMobile) {
container.style.maxWidth = 'calc(100vw - 20px)';
container.style.minWidth = '280px';
container.style.left = '10px';
container.style.right = '10px';
container.style.width = 'auto';
} else {
if (btnCfg.position === 'left') {
container.style.left = (ctaRect.left) + 'px';
} else {
container.style.right = (window.innerWidth - ctaRect.right) + 'px';
}
}
// Adiciona seta estilo chat (ajustada para mobile)
const arrow = document.createElement('div');
arrow.style.position = 'absolute';
arrow.style.width = '0';
arrow.style.height = '0';
arrow.style.border = (modalIsMobile ? '8px' : '10px') + ' solid transparent';
arrow.style.borderTopColor = config.settings.backgroundColor || '#ffffff';
arrow.style.bottom = modalIsMobile ? '-16px' : '-20px';
if (modalIsMobile) {
// Em mobile, posiciona a seta baseada na posição do botão
const arrowPosition = btnCfg.position === 'left' ? '30px' : 'calc(100% - 30px)';
arrow.style.left = arrowPosition;
} else {
arrow.style.left = btnCfg.position === 'left' ? '25px' : 'auto';
arrow.style.right = btnCfg.position === 'right' ? '25px' : 'auto';
}
container.appendChild(arrow);
}
}
});
};
// Função global para inicializar o jogo (nova abordagem)
window.sellflux_game = function(settings, prizes, apiUrl) {
// Limpar body antes de criar novo jogo
document.body.innerHTML = '';
const config = {
settings: {
title: 'Gire e Ganhe!',
subtitle: 'Preencha seus dados e gire a roleta para ganhar prêmios incríveis!',
buttonText: 'Girar a Roleta!',
buttonOpenModalText: 'SORTEIO',
logoUrl: '',
bannerUrl: '',
backgroundColor: '#000000',
titleColor: '#ffffff',
buttonColor: '#f56a00',
infoColor: '#ffffff',
borderColor: '#000000',
reappearanceFrequency: 0, // em horas sendo zero sempre que voltar
openingDelay: 0, // em segundos sendo zero imediatamente,
custom_keys: [],
email_field: true,
phone_field: true,
name_field: true,
showPlayAgain: true, // VALOR PADRÃO
...settings // MESCLA configurações fornecidas preservando padrões
},
prizes: prizes || [],
apiUrl: apiUrl || '/api/webhook/lead'
};
initRouletteGame(config);
};
// Função global para modal com controle de frequência de reaparecimento
window.sellflux_game_modal = function(settings, prizes, apiUrl) {
const config = {
settings: {
title: 'Gire e Ganhe!',
subtitle: 'Preencha seus dados e gire a roleta para ganhar prêmios incríveis!',
buttonText: 'Girar a Roleta!',
buttonOpenModalText: 'SORTEIO',
logoUrl: '',
bannerUrl: '',
backgroundColor: '#000000',
titleColor: '#ffffff',
buttonColor: '#f56a00',
infoColor: '#ffffff',
borderColor: '#000000',
reappearanceFrequency: 0, // em horas sendo zero sempre que voltar
openingDelay: 0, // em segundos sendo zero imediatamente,
custom_keys: [],
email_field: true,
phone_field: true,
name_field: true,
showPlayAgain: true, // VALOR PADRÃO
...settings // MESCLA configurações fornecidas preservando padrões
},
prizes: prizes || [],
apiUrl: apiUrl || '/api/webhook/lead'
};
// Sempre criar o botão flutuante primeiro
createFloatingButton(config);
const cookieManager = manageCookies();
const reappearanceFrequencyHours = config.settings.reappearanceFrequency || 0;
// Verificar se deve mostrar o modal automaticamente baseado na frequência de reaparecimento
if (cookieManager.canShowModalAgain(reappearanceFrequencyHours)) {
// Pode mostrar o modal automaticamente - aplicar delay se configurado
const openingDelaySeconds = config.settings.openingDelay || 0;
if (openingDelaySeconds > 0) {
setTimeout(() => {
showModalDirectly(config);
}, openingDelaySeconds * 1000);
} else {
showModalDirectly(config);
}
}
// Se não pode mostrar automaticamente, o botão flutuante já foi criado acima
};
// ==== EXIBE O MODAL ISOLADO ====
function showModalDirectly(config, isAnchoredMode = false) {
// Evita recriar/atualizar o modal caso ele já esteja aberto
// Isso impede que o timer de abertura automática "derrube" a edição em andamento
if (document.getElementById('sellflux-modal-overlay')) {
return; // Modal já visível. Não faz nada.
}
const shadowRoot = createModalOverlay(config, isAnchoredMode);
initRouletteGameModal(config, shadowRoot);
return shadowRoot; // Retorna shadowRoot para ajustes externos
}
// Função para criar o botão flutuante
function createFloatingButton(config) {
// Remover botão existente se houver
const existingButton = document.getElementById('sellflux-floating-button');
if (existingButton) {
existingButton.remove();
}
const floatingButton = document.createElement('div');
floatingButton.id = 'sellflux-floating-button';
if(config.settings.buttonOpenModalText){
floatingButton.innerHTML = `
`;
}
// Adicionar estilos para o botão flutuante
const buttonStyles = document.createElement('style');
buttonStyles.id = 'sellflux-floating-button-styles';
// Remover estilos existentes se houver
const existingStyles = document.getElementById('sellflux-floating-button-styles');
if (existingStyles) {
existingStyles.remove();
}
buttonStyles.textContent = `
#sellflux-floating-button {
position: fixed;
bottom: 20px;
left: 20px;
z-index: 999999;
animation: fadeIn 1s ease-in-out;
}
.sfx_floating-button-content {
background: ${config.settings.backgroundColor || '#000000'};
color: ${config.settings.infoColor || '#ffffff'};
border: none;
border-radius: 50px;
padding: 15px 25px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
}
.sfx_floating-button-content:hover {
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.4);
opacity: 0.9;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@media (max-width: 768px) {
#sellflux-floating-button {
bottom: 15px;
left: 15px;
}
.sfx_floating-button-content {
padding: 12px 20px;
font-size: 14px;
}
}
`;
document.head.appendChild(buttonStyles);
document.body.appendChild(floatingButton);
// Adicionar evento de clique para abrir o modal (sem remover o botão)
floatingButton.addEventListener('click', () => {
showModalDirectly(config);
});
}
// ==== CRIAÇÃO DO MODAL COM ISOLAMENTO VIA SHADOW DOM ====
function createModalOverlay(config, isAnchoredMode = false) {
// Remover overlay existente se houver
const existingOverlay = document.getElementById('sellflux-modal-overlay');
if (existingOverlay) {
existingOverlay.remove();
}
// Wrapper que ficará no DOM principal apenas para posicionamento
const overlay = document.createElement('div');
overlay.id = 'sellflux-modal-overlay';
overlay.style.position = 'fixed';
// Configuração diferenciada para modo ancorado (chat)
if (isAnchoredMode) {
overlay.style.width = '0';
overlay.style.height = '0';
overlay.style.overflow = 'visible';
} else {
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
}
overlay.style.zIndex = '1000000';
// Cria Shadow Root para isolar estilos
const shadow = overlay.attachShadow({ mode: 'open' });
// Estrutura interna do modal
const overlayStyles = isAnchoredMode ? `
.sfx_modal-overlay {
position: relative;
width: auto;
height: auto;
background-color: transparent;
z-index: 1000000;
display: block;
pointer-events: none;
}` : `
.sfx_modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%; /* ocupa viewport, sem gerar scroll extra */
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000000;
display: flex;
justify-content: center;
align-items: center;
padding: 40px ; /* cria respiro top/bottom */
box-sizing: border-box;
animation: fadeInModal 0.3s ease-out;
}`;
shadow.innerHTML = `
`;
// Patch temporário para que as chamadas existentes continuem funcionando
(function patchDomForShadow(dom, sr){
const origGetById = dom.getElementById.bind(dom);
dom.getElementById = function(id){ return sr.getElementById(id) || origGetById(id); };
const origQuery = dom.querySelector.bind(dom);
dom.querySelector = function(sel){ return sr.querySelector(sel) || origQuery(sel); };
})(document, shadow);
document.body.appendChild(overlay);
// Função para fechar modal (sem remover botão flutuante)
function closeModal() {
overlay.remove();
}
// Adicionar evento para fechar o modal
const closeBtn = shadow.getElementById('modal-close-btn');
closeBtn.addEventListener('click', closeModal);
// Fechar modal ao clicar no overlay (fora do container)
shadow.querySelector('.sfx_modal-overlay').addEventListener('click', (e) => {
if (e.target.classList && e.target.classList.contains('sfx_modal-overlay')) {
closeModal();
}
});
// Fechar modal com ESC
function escHandler(e) {
if (e.key === 'Escape') {
closeModal();
document.removeEventListener('keydown', escHandler);
}
}
document.addEventListener('keydown', escHandler);
// Retornar shadowRoot para uso posterior
return shadow;
}
// ==== INJEÇÃO DE ESTILOS ====
// Permite escolher onde o