diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md
index 57c1fed..b3233cb 100644
--- a/docs/IMPLEMENTATION_STATUS.md
+++ b/docs/IMPLEMENTATION_STATUS.md
@@ -68,6 +68,12 @@ Working:
advanced; knockout matches whose participants did not include the
user's stored pick are tracked as `forfeitedKnockoutPoints` and
excluded from their per-user maximum-possible score
+- tournament and league leaderboards render the Total column as
+ `score / per-user maximum possible`
+ (`tournament.rules.totalMaximumPoints − forfeitedKnockoutPoints`)
+ with an inline footnote when any player has forfeited points; the
+ global leaderboard and profile still display earned totals only
+ because a cross-tournament maximum is not yet exposed
### 2.4 League Participation
diff --git a/docs/QA_CHECKLIST.md b/docs/QA_CHECKLIST.md
index 7703cac..a803d0d 100644
--- a/docs/QA_CHECKLIST.md
+++ b/docs/QA_CHECKLIST.md
@@ -284,6 +284,13 @@ Expected:
- tournament leaderboard reflects tournament-scope scores
- round columns match the structure
- prize UI only appears when enabled
+- the Total column renders as `score / per-user max`. When no group has
+ closed with corrected seeding yet, every row's max equals
+ `tournament.rules.totalMaximumPoints`. After a group is closed and at
+ least one user has a knockout pick whose actual participants did not
+ include their stored team, that row's max drops by
+ `pointsPerCorrect` for each such match and an explanatory footnote
+ appears below the table
## 15. League Leaderboard
@@ -296,6 +303,8 @@ Expected:
- league leaderboard is filtered to that league
- tournament leaderboard is not replaced by league-only results
+- the Total column on the league board renders the same
+ `score / per-user max` treatment as the tournament leaderboard
## 16. League Prediction Copy Flow
diff --git a/src/i18n/messages/en.js b/src/i18n/messages/en.js
index 43d77c5..9da2116 100644
--- a/src/i18n/messages/en.js
+++ b/src/i18n/messages/en.js
@@ -344,6 +344,10 @@ export const EN = {
sfPts: 'SF',
finalPts: 'Final',
total: 'Total',
+ effectiveMaxTooltip:
+ 'Score / your maximum possible. Knockout matches whose actual teams did not include your stored pick are excluded from your maximum.',
+ effectiveMaxNote:
+ 'Total shows score / per-user maximum. Knockout matches whose actual teams did not include your stored pick are excluded from your maximum.',
you: 'You',
prizePool: 'Prize Pool',
participants: 'Participants',
diff --git a/src/i18n/messages/es.js b/src/i18n/messages/es.js
index d330193..7255331 100644
--- a/src/i18n/messages/es.js
+++ b/src/i18n/messages/es.js
@@ -344,6 +344,10 @@ export const ES = {
sfPts: 'SF',
finalPts: 'Final',
total: 'Total',
+ effectiveMaxTooltip:
+ 'Puntaje / tu máximo posible. Los partidos de eliminación cuyos equipos reales no incluyen tu pick guardado quedan fuera de tu máximo.',
+ effectiveMaxNote:
+ 'El total muestra puntaje / máximo posible por usuario. Los partidos de eliminación cuyos equipos reales no incluyen tu pick guardado quedan fuera de tu máximo.',
you: 'Tú',
prizePool: 'Fondo de Premios',
participants: 'Participantes',
diff --git a/src/i18n/messages/it.js b/src/i18n/messages/it.js
index fa012ed..a5703fb 100644
--- a/src/i18n/messages/it.js
+++ b/src/i18n/messages/it.js
@@ -346,6 +346,10 @@ export const IT = {
sfPts: 'Semi.',
finalPts: 'Finale',
total: 'Totale',
+ effectiveMaxTooltip:
+ 'Punteggio / massimo possibile. Le partite a eliminazione le cui squadre reali non includono la tua scelta salvata sono escluse dal tuo massimo.',
+ effectiveMaxNote:
+ 'Il totale mostra punteggio / massimo possibile per utente. Le partite a eliminazione le cui squadre reali non includono la tua scelta salvata sono escluse dal tuo massimo.',
you: 'Tu',
prizePool: 'Montepremi',
participants: 'Partecipanti',
diff --git a/src/i18n/messages/nl.js b/src/i18n/messages/nl.js
index e0eba6b..5abb60b 100644
--- a/src/i18n/messages/nl.js
+++ b/src/i18n/messages/nl.js
@@ -346,6 +346,10 @@ export const NL = {
sfPts: 'Half',
finalPts: 'Finale',
total: 'Totaal',
+ effectiveMaxTooltip:
+ 'Score / jouw maximaal mogelijke. Knock-outwedstrijden waarvan de echte teams jouw opgeslagen keuze niet bevatten, tellen niet mee voor jouw maximum.',
+ effectiveMaxNote:
+ 'Totaal toont score / persoonlijk maximum. Knock-outwedstrijden waarvan de echte teams jouw opgeslagen keuze niet bevatten, tellen niet mee voor jouw maximum.',
you: 'Jij',
prizePool: 'Prijzenpot',
participants: 'Deelnemers',
diff --git a/src/i18n/messages/pt.js b/src/i18n/messages/pt.js
index a0c747a..e0dda01 100644
--- a/src/i18n/messages/pt.js
+++ b/src/i18n/messages/pt.js
@@ -346,6 +346,10 @@ export const PT = {
sfPts: 'Semi.',
finalPts: 'Final',
total: 'Total',
+ effectiveMaxTooltip:
+ 'Pontuação / seu máximo possível. Os jogos de mata-mata cujas equipes reais não incluem o seu palpite guardado ficam fora do seu máximo.',
+ effectiveMaxNote:
+ 'O total mostra pontuação / máximo possível por usuário. Os jogos de mata-mata cujas equipes reais não incluem o seu palpite guardado ficam fora do seu máximo.',
you: 'Você',
prizePool: 'Premiação',
participants: 'Participantes',
diff --git a/src/index.css b/src/index.css
index a486730..f60cb1c 100644
--- a/src/index.css
+++ b/src/index.css
@@ -3004,6 +3004,14 @@ html.theme-light .nav-dropdown-settings,
overflow: hidden;
}
+.leaderboard-table-footnote {
+ color: var(--text-muted);
+ font-size: 0.85rem;
+ line-height: 1.45;
+ margin: 0;
+ padding: 0.75rem 1.5rem 1.25rem;
+}
+
.app-table {
width: 100%;
border-collapse: collapse;
diff --git a/src/pages/Leaderboard.jsx b/src/pages/Leaderboard.jsx
index 90f8d9a..d9c321f 100644
--- a/src/pages/Leaderboard.jsx
+++ b/src/pages/Leaderboard.jsx
@@ -105,6 +105,15 @@ export default function Leaderboard() {
? currentLeague.description || t('leaderboard.leagueDescription')
: tournamentName || t('home.noTournaments');
+ // The tournament-wide maximum a perfect bracket can score. We use it
+ // together with each player's `forfeitedKnockoutPoints` (knockout matches
+ // whose actual participants did not include the player's stored pick) to
+ // render a per-user effective max alongside the total.
+ const tournamentMaxPoints = tournament?.rules?.totalMaximumPoints || 0;
+ const someoneForfeited = players.some(
+ (player) => (player.forfeitedKnockoutPoints || 0) > 0
+ );
+
if (loading) {
return (
+ {t('leaderboard.effectiveMaxNote')}
+
{formatNumber(player.totalScore || 0)}
+ {tournamentMaxPoints ? (
+
+ {` / ${formatNumber(effectiveMax)}`}
+
+ ) : null}
);
@@ -339,6 +360,11 @@ export default function Leaderboard() {
+ {someoneForfeited ? (
+
+ {t('leaderboard.effectiveMaxNote')} +
+ ) : null} )}