✓Nenhuma aposta SAFE foi liberadaO Risk Gate não força entradas. ${radarCount ? `${radarCount} jogo${radarCount === 1 ? '' : 's'} está${radarCount === 1 ? '' : 'ão'} em observação no Radar.` : 'Os times monitorados ficam no Radar, sem recomendação de aposta.'}Ver times no Radar ↓
`;
$("picks").setAttribute("aria-busy", "false");
renderRadar(state);
const tickets = state.tickets && typeof state.tickets === "object" ? state.tickets : {};
$("tickets").innerHTML = ["safe", "balanced", "freebet"].map((key) => ticketCard(key, tickets[key])).join("");
$("tickets").setAttribute("aria-busy", "false");
renderHealth(state);
}
function setRegionsBusy(isBusy) {
["picks", "radar-cards", "tickets", "health"].forEach((id) => $(id)?.setAttribute("aria-busy", String(isBusy)));
}
let activeController = null;
let toastTimer = null;
function showToast(message) {
const toast = $("toast");
if (!toast) return;
toast.textContent = message;
toast.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toast.classList.remove("show"), 2300);
}
async function load({ force = false, announce = false } = {}) {
if (activeController && !force) return;
if (activeController) activeController.abort();
const controller = new AbortController();
activeController = controller;
const refreshButton = $("refresh-ui");
setRegionsBusy(true);
refreshButton.classList.add("loading");
refreshButton.disabled = true;
refreshButton.setAttribute("aria-label", "Atualizando painel");
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
try {
const response = await fetch("/api/state", {
cache: "no-store",
headers: { Accept: "application/json" },
signal: controller.signal
});
if (!response.ok) throw new Error(`servidor respondeu ${response.status}`);
const state = await response.json();
renderState(state);
if (announce) showToast("Painel atualizado com sucesso.");
} catch (error) {
if (error.name === "AbortError" && activeController !== controller) return;
setStatus("error");
const message = error.name === "AbortError"
? "A atualização demorou mais que o esperado. Tente novamente."
: `Falha ao carregar o painel: ${error.message}`;
warnings([message]);
setRegionsBusy(false);
if (announce) showToast("Não foi possível atualizar o painel.");
} finally {
clearTimeout(timeout);
if (activeController === controller) {
activeController = null;
refreshButton.classList.remove("loading");
refreshButton.disabled = false;
refreshButton.setAttribute("aria-label", "Atualizar painel");
}
}
}
window.evalOdd = function evalOdd(input, probability) {
if (!input) return;
const result = input.closest(".oddbox")?.querySelector(".oddresult");
if (!result) return;
const odd = finite(input.value);
const p = finite(probability);
result.className = "oddresult";
if (odd === null || odd <= 1) {
result.textContent = input.value ? "Digite uma odd válida, acima de 1,00." : "Digite uma odd para estimar o valor esperado.";
return;
}
if (p === null || p <= 0) {
result.textContent = "Probabilidade indisponível para esta comparação.";
return;
}
const expectedValue = p * odd - 1;
const fairOdd = 1 / p;
if (expectedValue >= .03) {
result.classList.add("good");
result.textContent = `Valor positivo · EV +${num(expectedValue * 100, 1)}% · odd justa ${num(fairOdd, 2)}`;
} else if (expectedValue >= -.02) {
result.classList.add("fair");
result.textContent = `Preço próximo do justo · EV ${num(expectedValue * 100, 1)}% · justa ${num(fairOdd, 2)}`;
} else {
result.classList.add("weak");
result.textContent = `Preço abaixo do modelo · EV ${num(expectedValue * 100, 1)}% · justa ${num(fairOdd, 2)}`;
}
};
async function writeClipboard(text) {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
return;
}
const field = document.createElement("textarea");
field.value = text;
field.setAttribute("readonly", "");
field.className = "clipboard-fallback";
document.body.appendChild(field);
field.select();
const copied = document.execCommand("copy");
field.remove();
if (!copied) throw new Error("cópia indisponível");
}
window.copyText = async function copyText(button) {
if (!button) return;
const text = button.dataset.copy || "";
if (!text) return;
try {
await writeClipboard(text);
showToast("Copiado para a área de transferência.");
} catch (_error) {
showToast("Não foi possível copiar automaticamente.");
}
};
document.addEventListener("click", (event) => {
const copyButton = event.target.closest(".copybtn");
if (copyButton) window.copyText(copyButton);
const scrollButton = event.target.closest("[data-scroll-target]");
if (scrollButton) $(scrollButton.dataset.scrollTarget)?.scrollIntoView({ behavior: "smooth", block: "start" });
});
document.addEventListener("input", (event) => {
const input = event.target.closest(".market-odd-input");
if (input) window.evalOdd(input, input.dataset.probability);
});
$("refresh-ui").addEventListener("click", () => load({ force: true, announce: true }));
load();
setInterval(() => {
if (!document.hidden) load();
}, REFRESH_INTERVAL);