Advertise the jackpot that will actually be paid (B-65)

participant_count and jackpot_sats were computed over every round_participants
row, while the draw only picks from confirmed participants and the payout only
spends their sats. So the advertised jackpot could exceed the one paid out, and a
player whose bet was later abandoned appeared in the count and then vanished
again.

Counting only confirmed rows would have fixed the arithmetic and broken something
else: the player who just bet would see neither themselves nor their money for a
whole block. So this is the same confirmed/in-flight split the balance already
exposes (balance_sats vs pending_balance_sats): participant_count and
jackpot_sats are now the confirmed, authoritative figures, and
pending_participant_count/pending_jackpot_sats/has_pending_bets report what is in
flight — inclusive figures, not deltas, matching the balance pair's convention.

/'s round card shows the confirmed numbers big and the difference as an amber
"+N in attesa" suffix, reusing .balance-pending's colour for the same "not
settled yet" meaning. The two new spans render from server data through t(), so
they carry no data-i18n and onLanguageChange() repaints them from the last
response — the one-mechanism-per-element rule. Both strings are in all 7
languages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-04 11:52:10 +02:00
co-authored by Claude Opus 5
parent c0314e2bf0
commit c4b2dc3ea2
8 changed files with 167 additions and 32 deletions
+48 -10
View File
@@ -368,6 +368,49 @@ async function checkLastRoundResult() {
let lastJackpotValue = null;
// The round's own confirmed/in-flight split (B-65). The big numbers are the
// confirmed ones — the players the draw will pick from and the pool the payout
// will actually spend — because a jackpot advertised larger than the one paid out
// is the kind of gap nobody forgives. What has been bet but hasn't confirmed yet
// is shown next to them instead of being folded in, so the player who just bet
// still sees their own bet immediately (the same reasoning as the amber
// pending balance, see setBalanceDisplay).
//
// Kept out of the [data-i18n] mechanism on purpose: these come from server data,
// so onLanguageChange() re-renders them through t() like every other dynamic bit.
let lastRoundStats = null;
function renderRoundStats(data) {
lastRoundStats = data;
document.getElementById('round-players').textContent = data.participant_count;
const jackpotEl = document.getElementById('round-jackpot');
const jackpotValue = data.jackpot_sats / SATS_PER_PLM;
jackpotEl.textContent = formatPlm(data.jackpot_sats);
if (lastJackpotValue !== null && jackpotValue !== lastJackpotValue) {
jackpotEl.classList.remove('jackpot-bump');
void jackpotEl.offsetWidth; // restart the animation
jackpotEl.classList.add('jackpot-bump');
}
lastJackpotValue = jackpotValue;
// pending_* are inclusive of the confirmed figures (like pending_balance_sats),
// so what's shown alongside is the difference.
const pendingPlayers = (data.pending_participant_count || 0) - data.participant_count;
const pendingJackpotSats = (data.pending_jackpot_sats || 0) - data.jackpot_sats;
const show = !!data.has_pending_bets && pendingPlayers > 0;
const playersPendingEl = document.getElementById('round-players-pending');
playersPendingEl.textContent = show ? t('round.playersPending', { n: pendingPlayers }) : '';
playersPendingEl.classList.toggle('hidden', !show);
const jackpotPendingEl = document.getElementById('round-jackpot-pending');
jackpotPendingEl.textContent = show
? t('round.jackpotPending', { amount: formatPlm(pendingJackpotSats) })
: '';
jackpotPendingEl.classList.toggle('hidden', !show);
}
let timerHitZero = false;
function updateRoundTimer() {
@@ -451,16 +494,7 @@ async function refreshRound() {
: t('round.none');
betAmountSats = data.bet_amount_sats;
renderBetButton();
document.getElementById('round-players').textContent = data.participant_count;
const jackpotEl = document.getElementById('round-jackpot');
const jackpotValue = data.jackpot_sats / SATS_PER_PLM;
jackpotEl.textContent = formatPlm(data.jackpot_sats);
if (lastJackpotValue !== null && jackpotValue !== lastJackpotValue) {
jackpotEl.classList.remove('jackpot-bump');
void jackpotEl.offsetWidth; // restart the animation
jackpotEl.classList.add('jackpot-bump');
}
lastJackpotValue = jackpotValue;
renderRoundStats(data);
if (data.server_time) serverTimeOffsetMs = new Date(data.server_time) - new Date();
roundCloseAt = data.closes_at ? new Date(data.closes_at) : null;
updateRoundTimer();
@@ -858,6 +892,10 @@ function connectRoundEvents() {
function onLanguageChange() {
renderBetButton();
renderChainStatusBar(); // repaints from remembered state, without waiting for the next poll
// Same reason, for the round's "+N in attesa" suffixes (B-65): repaint from what
// was last received rather than leaving them in the old language until the
// refreshRound() below happens to come back.
if (lastRoundStats) renderRoundStats(lastRoundStats);
if (token) {
refreshRound();
refreshMe();