`);
win.document.close();
}/* ====== ESTATÍSTICAS ====== */async function updateStats() {
try {
const playersSnap = await getDocs(collection(db, "players"));
const matchesSnap = await getDocs(collection(db, "matches"));
const players = [];
playersSnap.forEach(d => players.push(d.data()));
const matches = [];
matchesSnap.forEach(d => matches.push(d.data()));
window.allMatches = matches;
if (typeof calculatePlayerLaneStats === 'function') {
calculatePlayerLaneStats(matches, players);
} else {
console.error("calculatePlayerLaneStats não está definida");
}
const totalPlayers = players.length;
const totalMatches = matches.length;
const totalPoints = players.reduce((sum, p) => sum + (p.pontos || 0), 0);
const avgPoints = totalPlayers > 0 ? (totalPoints / totalPlayers).toFixed(1) : 0;
const topPlayer = players.length > 0 ?
players.reduce((max, p) => (p.pontos || 0) > (max.pontos || 0) ? p : max) :
null;
const azulWins = matches.filter(m => m.vencedor === 'azul').length;
const vermelhoWins = matches.filter(m => m.vencedor === 'vermelho').length;
const statsContent = document.getElementById("statsContent");
if (statsContent) {
statsContent.innerHTML = `
Total de Jogadores ${totalPlayers}
Total de Partidas ${totalMatches}
Média de Pontos ${avgPoints}
Vitórias Vermelho ${vermelhoWins}
${topPlayer ? `
Top Player ${topPlayer.nickname}
${topPlayer.pontos || 0} pontos
` : ''}
`;
}
} catch (error) {
console.error("Erro ao carregar estatísticas:", error);
}
}/* ====== RANKING INICIAL ====== */async function loadInitialRanking() {
try {
// Verificar se db está definido
if (typeof db === 'undefined' || !db) {
console.error("Firebase não está inicializado!");
return;
}
console.log("Carregando ranking inicial...");
const snap = await getDocs(collection(db, "players"));
const arr = [];
snap.forEach(d => arr.push(d.data()));
arr.forEach(p => {
p.pontos = p.pontos || 0;
p.vitorias = p.vitorias || 0;
p.derrotas = p.derrotas || 0;
p.mvp = p.mvp || 0;
p.ace = p.ace || 0;
});
window.rankingData = arr;
const matchesSnap = await getDocs(collection(db, "matches"));
const matches = [];
matchesSnap.forEach(d => matches.push(d.data()));
window.allMatches = matches;
if (typeof calculatePlayerLaneStats === 'function') {
calculatePlayerLaneStats(matches, arr);
} else {
console.error("calculatePlayerLaneStats não está definida");
}
if (typeof renderRanking === 'function') {
renderRanking();
}
// Carregar tabela de elos inicial
if (typeof calcularEloCompleto === 'function') {
const elosCalculados = arr.map(player => calcularEloCompleto(player));
// Ordenar usando calcularValorElo para considerar elo+divisão+pdls corretamente (maior primeiro)
elosCalculados.sort((a, b) => {
const valorA = calcularValorElo(a);
const valorB = calcularValorElo(b);
return valorB - valorA;
});
if (typeof loadEloTable === 'function') {
window.rankingData = arr;
const filterAtual = window.currentEloTableFilter || 'all';
loadEloTable(elosCalculados, filterAtual);
} else {
console.error("loadEloTable não está definida");
}
} else {
console.error("calcularEloCompleto não está definida");
}
} catch (error) {
console.error("Erro ao carregar ranking:", error);
}
}/* ====== PROCESSAR PARÂMETROS DE URL ====== */function processUrlParams() {
const urlParams = new URLSearchParams(window.location.search);
const voteType = urlParams.get('vote');
const matchId = urlParams.get('match');
if (voteType && matchId) {
createVotePage(matchId, voteType);
window.history.replaceState({}, document.title, window.location.pathname);
}
}/* ====== INICIALIZAÇÃO FINAL ====== */// Aguardar DOM estar completamente pronto
function startApp() {
// Carregar imagens dos elos do Firebase
if (typeof loadEloImagesFromFirebase === 'function') {
loadEloImagesFromFirebase();
} else if (typeof loadEloImages === 'function') {
loadEloImages();
}
// Aguardar um pouco mais para garantir que todos os elementos estejam no DOM
setTimeout(() => {
// Inicializar UI
if (typeof updateUIForUserType === 'function') {
updateUIForUserType();
}// Carregar dados iniciais
if (typeof loadInitialRanking === 'function') {
loadInitialRanking();
}
}, 100);
}// Inicializar quando DOM estiver pronto
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startApp);
} else {
// DOM já está pronto
startApp();
}// Listener em tempo real para players (atualiza ranking e tabela de elos automaticamente)
// Verificar se db está definido antes de criar o listener
function setupPlayersListener() {
if (typeof db !== 'undefined' && db) {
onSnapshot(collection(db, "players"), async (snap) => {
try {
const arr = [];
snap.forEach(d => arr.push(d.data()));
arr.forEach(p => {
p.pontos = p.pontos || 0;
p.vitorias = p.vitorias || 0;
p.derrotas = p.derrotas || 0;
p.mvp = p.mvp || 0;
p.ace = p.ace || 0;
});
window.rankingData = arr;
// Usar matches já em cache (atualizado pelo onSnapshot de matches)
// Só busca do Firebase se ainda não tiver cache
let matches = window.allMatches;
if (!matches) {
const matchesSnap = await getDocs(collection(db, "matches"));
matches = [];
matchesSnap.forEach(d => matches.push(d.data()));
window.allMatches = matches;
}
if (typeof calculatePlayerLaneStats === 'function') {
calculatePlayerLaneStats(matches, arr);
} else {
console.error("calculatePlayerLaneStats não está definida");
}
if (typeof renderRanking === 'function') {
renderRanking();
}
// Atualizar tabela de elos
// Primeiro calcular elos sem filtro para determinar lane principal
if (typeof calcularEloCompleto === 'function') {
const elosCalculados = arr.map(player => calcularEloCompleto(player));
elosCalculados.sort((a, b) => {
const nivelA = ELOS[a.elo]?.nivel || 0;
const nivelB = ELOS[b.elo]?.nivel || 0;
if (nivelB !== nivelA) return nivelB - nivelA;
if (a.divisao !== b.divisao) return a.divisao - b.divisao;
return (b.pdls || 0) - (a.pdls || 0);
});
// Armazenar dados dos players para uso no filtro
window.rankingData = arr;
const eloFilter = document.getElementById('eloFilter');
if (typeof loadEloTable === 'function') {
loadEloTable(elosCalculados, eloFilter?.value || 'all');
} else {
console.error("loadEloTable não está definida");
}
} else {
console.error("calcularEloCompleto não está definida");
// Armazenar dados dos players mesmo se não conseguir calcular elos
window.rankingData = arr;
}
} catch (error) {
console.error("Erro no listener de players:", error);
}
}, (error) => {
console.error("Erro ao configurar listener de players:", error);
});
} else {
// Firebase não está pronto ainda, tentar novamente
setTimeout(setupPlayersListener, 500);
}
}// Configurar listener quando Firebase estiver pronto
setupPlayersListener();// Verificar parâmetros de URL
window.addEventListener('load', processUrlParams);// Atualizar estatísticas periodicamente
setInterval(updateStats, 30000);// Fechar modais ao clicar fora
const voteModal = document.getElementById('voteModal');
if (voteModal) {
voteModal.addEventListener('click', (e) => {
if (e.target.id === 'voteModal') {
e.target.classList.remove('show');
}
});
}/* ====== SISTEMA DE UPLOAD DE AVATAR DO RANKING ====== */// Event listener para clicar no avatar
document.addEventListener('click', (e) => {
const avatarEl = e.target.closest('.rank-avatar[data-nick]');
if (avatarEl && isAdmin) {
const nickname = avatarEl.dataset.nick;
if (nickname) {
openAvatarUploadModal(nickname);
}
}
});function openAvatarUploadModal(nickname) {
const modal = document.getElementById('avatarUploadModal');
const playerNameEl = document.getElementById('avatarPlayerName');
const previewEl = document.getElementById('avatarPreview');
const letterEl = document.getElementById('avatarLetter');
const fileInput = document.getElementById('avatarFileInput');
const saveBtn = document.getElementById('saveAvatarBtn');
if (!modal) return;
// Buscar dados do jogador
const player = window.rankingData?.find(p => p.nickname === nickname);
playerNameEl.textContent = nickname;
letterEl.textContent = nickname.charAt(0).toUpperCase();
// Mostrar avatar atual se existir
if (player?.avatarUrl) {
previewEl.innerHTML = `
`;
} else {
previewEl.innerHTML = `
${nickname.charAt(0).toUpperCase()} `;
}
// Resetar file input
fileInput.value = '';
saveBtn.disabled = true;
// Preview da nova imagem
fileInput.onchange = (e) => {
const file = e.target.files[0];
if (file) {
if (!file.type.startsWith('image/')) {
showToast('Por favor, selecione um arquivo de imagem!', 'error');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
previewEl.innerHTML = `
`;
saveBtn.disabled = false;
};
reader.readAsDataURL(file);
}
};
// Mostrar modal
modal.style.display = 'flex';
// Configurar botões
document.getElementById('cancelAvatarBtn').onclick = () => {
modal.style.display = 'none';
};
document.getElementById('removeAvatarBtn').onclick = async () => {
if (!confirm(`Remover avatar de ${nickname}?`)) return;
try {
const playerRef = doc(db, "players", nickname);
await updateDoc(playerRef, {
avatarUrl: null
});
showToast('Avatar removido com sucesso!', 'success');
modal.style.display = 'none';
// Recarregar ranking
if (typeof loadInitialRanking === 'function') {
loadInitialRanking();
}
} catch (error) {
console.error('Erro ao remover avatar:', error);
showToast('Erro ao remover avatar!', 'error');
}
};
document.getElementById('saveAvatarBtn').onclick = async () => {
const file = fileInput.files[0];
if (!file) {
showToast('Selecione uma imagem!', 'error');
return;
}
saveBtn.disabled = true;
saveBtn.textContent = 'Salvando...';
try {
// Converter para base64 com Promise para poder usar try/catch
const base64Image = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = () => reject(new Error('Falha ao ler o arquivo'));
reader.readAsDataURL(file);
});
// Salvar no Firebase
const playerRef = doc(db, "players", nickname);
await updateDoc(playerRef, {
avatarUrl: base64Image
});
showToast('Avatar atualizado com sucesso!', 'success');
modal.style.display = 'none';
// Recarregar ranking
if (typeof loadInitialRanking === 'function') {
loadInitialRanking();
}
} catch (error) {
console.error('Erro ao salvar avatar:', error);
showToast('Erro ao salvar avatar!', 'error');
} finally {
saveBtn.disabled = false;
saveBtn.textContent = 'Salvar Avatar';
}
};
}// Fechar modal ao clicar fora
document.getElementById('avatarUploadModal')?.addEventListener('click', (e) => {
if (e.target.id === 'avatarUploadModal') {
e.target.style.display = 'none';
}
});// Event listener para upload de campeoes favoritos (2 e 3)
document.addEventListener('click', (e) => {
const placeholder = e.target.closest('.rank-champ-mini-placeholder[data-nick]');
if (placeholder && isAdmin) {
const nickname = placeholder.dataset.nick;
const champKey = placeholder.dataset.champ; // 'champ2' or 'champ3'
if (nickname && champKey) {
openChampUploadModal(nickname, champKey);
}
}
// Clique no icone do campeo para remover (admin)
const champIcon = e.target.closest('.rank-champ-mini[data-nick]');
if (champIcon && isAdmin) {
const nickname = champIcon.dataset.nick;
const champKey = champIcon.dataset.champ;
if (nickname && champKey) {
openChampUploadModal(nickname, champKey);
}
}
});function openChampUploadModal(nickname, champKey) {
const label = champKey === 'champ2' ? '2° Campeão Favorito' : '3° Campeão Favorito';
const player = window.rankingData?.find(p => p.nickname === nickname);
const currentUrl = player?.[champKey + 'Url'] || null;// Criar input de arquivo dinamico
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.style.display = 'none';
document.body.appendChild(fileInput);fileInput.onchange = async (e) => {
const file = e.target.files[0];
document.body.removeChild(fileInput);
if (!file) return;
if (!file.type.startsWith('image/')) {
showToast('Selecione uma imagem valida!', 'error');
return;
}
if (file.size > 200 * 1024) {
showToast('Imagem muito grande! Max 200KB.', 'error');
return;
}
try {
showToast('Salvando campeo...', 'info');
const base64 = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (ev) => resolve(ev.target.result);
reader.onerror = () => reject(new Error('Falha ao ler arquivo'));
reader.readAsDataURL(file);
});
const playerRef = doc(db, 'players', nickname);
await updateDoc(playerRef, { [champKey + 'Url']: base64 });
showToast(label + ' salvo!', 'success');
if (typeof loadInitialRanking === 'function') loadInitialRanking();
} catch (err) {
showToast('Erro ao salvar: ' + err.message, 'error');
}
};// Se ja tem imagem, perguntar se quer substituir ou remover
if (currentUrl && isAdmin) {
const action = confirm(label + ' de ' + nickname + '\n\nOK = Substituir imagem\nCancelar = Remover imagem atual');
if (action) {
fileInput.click();
} else {
document.body.removeChild(fileInput);
// Remover imagem
(async () => {
try {
const playerRef = doc(db, 'players', nickname);
await updateDoc(playerRef, { [champKey + 'Url']: null });
showToast(label + ' removido!', 'success');
if (typeof loadInitialRanking === 'function') loadInitialRanking();
} catch (err) {
showToast('Erro ao remover: ' + err.message, 'error');
}
})();
}
} else {
fileInput.click();
}
}// Limpar monitoramento ao fechar
window.addEventListener('beforeunload', () => {
stopLiveVotingUpdates();
});// Atualizar variáveis globais (já inicializadas acima)
window.duos = duos;
window.isAdmin = isAdmin;/* ====== TOGGLE PAINÉIS AVANÇADOS ====== */
let advancedPanelsVisible = false;function closeAdvancedPanels() {
advancedPanelsVisible = false;
const resetEl = document.getElementById('resetElosSection');
const duoEl = document.getElementById('duoSection');
const btn = document.getElementById('toggleAdvancedPanels');
if (resetEl) resetEl.style.display = 'none';
if (duoEl) duoEl.style.display = 'none';
if (btn) btn.textContent = '⚙️ Painéis Avançados';
}document.addEventListener('click', function(e) {
if (e.target.id !== 'toggleAdvancedPanels') return;
advancedPanelsVisible = !advancedPanelsVisible;
const resetEl = document.getElementById('resetElosSection');
const duoEl = document.getElementById('duoSection');
if (resetEl) resetEl.style.display = advancedPanelsVisible ? 'block' : 'none';
if (duoEl) duoEl.style.display = advancedPanelsVisible ? 'block' : 'none';
e.target.textContent = advancedPanelsVisible ? '⚙️ Ocultar Painéis Avançados' : '⚙️ Painéis Avançados';
});/* ====== RESET DE TEMPORADA ====== */
document.addEventListener('click', async (e) => {
if (e.target.id !== 'resetElosBtn') return;
const senha = prompt('🔐 Digite a senha para resetar os elos:');
if (senha !== '2824') {
alert('Senha incorreta!');
return;
}
const confirma = confirm('⚠️ ATENÇÃO: Isso vai zerar TODOS os dados de todos os jogadores (vitórias, derrotas, PDLs, pontos, MVPs e ACEs).\n\nOs elos voltarão ao eloBase cadastrado.\n\nEssa ação NÃO pode ser desfeita. Confirmar?');
if (!confirma) return;
const btn = document.getElementById('resetElosBtn');
btn.disabled = true;
btn.textContent = 'Resetando...';
try {
const snap = await getDocs(collection(db, 'players'));
const batch = writeBatch(db);
snap.forEach(docSnap => {
const data = docSnap.data();
const ref = doc(db, 'players', docSnap.id);
// Resetar laneStats mantendo lanes configuradas (elo e prioridade)
let laneStatsReset = {};
if (data.laneStats) {
Object.keys(data.laneStats).forEach(lane => {
laneStatsReset[lane] = {
jogou: 0, venceu: 0, perdeu: 0, pdls: 0,
elo: data.laneStats[lane]?.elo || data.eloBase || 'platina',
prioridade: data.laneStats[lane]?.prioridade || 0
};
});
}
batch.update(ref, {
vitorias: 0,
derrotas: 0,
pontos: 0,
mvp: 0,
ace: 0,
partidas: 0,
winStreak: 0,
loseStreak: 0,
lastResult: null,
streaksResetadas: false,
bestWinStreak: 0,
bestLoseStreak: 0,
laneStats: laneStatsReset,
eloManual: (() => {
const eb = data.eloBase || 'platina';
const isMp = (eb === 'mestre' || eb === 'grão-mestre' || eb === 'challenger');
// pdlsIniciais do elo base (ex: Mestre=100, Grão-Mestre=600, Challenger=1000)
// Para exibir "0 PDLs" na tela precisamos usar o pdlsIniciais do elo
const pdlsBase = { 'ferro':0,'bronze':0,'prata':0,'ouro':0,'platina':0,'esmeralda':0,'diamante':0,'mestre':100,'grão-mestre':600,'challenger':1000 };
return {
enabled: true,
elo: eb,
divisao: isMp ? 0 : 4,
pdls: pdlsBase[eb] || 0
};
})()
});
});
await batch.commit();
showToast('✅ Reset concluído! Todos os elos voltaram ao padrão.', 'success');
if (typeof loadInitialRanking === 'function') loadInitialRanking();
} catch (err) {
console.error('Erro no reset:', err);
showToast('Erro ao resetar: ' + err.message, 'error');
} finally {
btn.disabled = false;
btn.textContent = '🔄 Resetar Todos os Elos (Nova Temporada)';
}
});/* ====== IMAGENS DOS ELOS (FIREBASE) ====== */const ELO_KEYS = ['ferro','bronze','prata','ouro','platina','esmeralda','diamante','mestre','grao_mestre','challenger'];
const ELO_LABELS = {
ferro: 'Ferro', bronze: 'Bronze', prata: 'Prata', ouro: 'Ouro',
platina: 'Platina', esmeralda: 'Esmeralda', diamante: 'Diamante',
mestre: 'Mestre', grao_mestre: 'Grão-Mestre', challenger: 'Challenger'
};async function loadEloImagesFromFirebase() {
try {
const snap = await getDoc(doc(db, 'config', 'eloImages'));
if (snap.exists()) {
eloImagesCache = snap.data() || {};
localStorage.setItem('eloImages', JSON.stringify(eloImagesCache));
}
} catch (e) {
// fallback para localStorage
const cached = localStorage.getItem('eloImages');
if (cached) {
try { eloImagesCache = JSON.parse(cached); } catch(e2) {}
}
}
renderEloImageGrid();
}function renderEloImageGrid() {
const grid = document.getElementById('eloImageGrid');
if (!grid) return;
grid.innerHTML = ELO_KEYS.map(key => {
const imgUrl = eloImagesCache[key] || '';
return `
${!imgUrl ? '🛡️ ' : ''}
${ELO_LABELS[key]}
${imgUrl ? '✏️ Trocar' : '📤 Upload'}
${imgUrl ? `
🗑️ Remover ` : ''}
`;
}).join('');
// Eventos upload
grid.querySelectorAll('.elo-image-upload-input').forEach(input => {
input.addEventListener('change', async (e) => {
const file = e.target.files[0];
const eloKey = e.target.dataset.elo;
if (!file || !eloKey) return;
if (!file.type.startsWith('image/')) {
showToast('Selecione uma imagem válida!', 'error');
return;
}
// Verificar tamanho (max 200KB para caber no Firestore)
if (file.size > 200 * 1024) {
showToast('Imagem muito grande! Use no máximo 200KB.', 'error');
return;
}
showToast('Salvando imagem...', 'info');
try {
// Ler o arquivo como base64 usando Promise (corrige bug de async dentro de onload)
const base64 = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (ev) => resolve(ev.target.result);
reader.onerror = () => reject(new Error('Falha ao ler arquivo'));
reader.readAsDataURL(file);
});
eloImagesCache[eloKey] = base64;
// Salvar no Firebase
const configRef = doc(db, 'config', 'eloImages');
const existing = await getDoc(configRef);
if (existing.exists()) {
await updateDoc(configRef, { [eloKey]: base64 });
} else {
await setDoc(configRef, eloImagesCache);
}
// Atualizar localStorage
localStorage.setItem('eloImages', JSON.stringify(eloImagesCache));
showToast(`✅ Imagem do ${ELO_LABELS[eloKey]} salva!`, 'success');
renderEloImageGrid();
if (typeof loadInitialRanking === 'function') loadInitialRanking();
} catch (err) {
console.error('Erro ao salvar imagem:', err);
showToast('Erro ao salvar imagem: ' + err.message, 'error');
}
});
});
// Eventos remover
grid.querySelectorAll('[data-remove-elo]').forEach(btn => {
btn.addEventListener('click', async () => {
const eloKey = btn.dataset.removeElo;
if (!confirm(`Remover imagem do ${ELO_LABELS[eloKey]}?`)) return;
try {
delete eloImagesCache[eloKey];
const configRef = doc(db, 'config', 'eloImages');
await updateDoc(configRef, { [eloKey]: null });
localStorage.setItem('eloImages', JSON.stringify(eloImagesCache));
showToast(`Imagem removida!`, 'success');
renderEloImageGrid();
if (typeof loadInitialRanking === 'function') loadInitialRanking();
} catch (err) {
showToast('Erro ao remover: ' + err.message, 'error');
}
});
});
}// Expor funções críticas ao escopo global (necessário pois estão em type="module")
window.calcularEloCompleto = calcularEloCompleto;
window.calcularValorElo = calcularValorElo;
window.renderRanking = renderRanking;
window.loadEloTable = loadEloTable;
window.calculatePlayerLaneStats = calculatePlayerLaneStats;
window.loadInitialRanking = loadInitialRanking;
window.updateStats = updateStats;
window.startApp = startApp;
window.setupPlayersListener = setupPlayersListener;// Log function
function log(message) {
const logElement = document.getElementById('log');
if (logElement) {
logElement.textContent = message + '\n' + logElement.textContent;
}
}