diff --git a/frontend/js/leaderboard/render.js b/frontend/js/leaderboard/render.js
index 4b0ffe1b..c23bb9d6 100644
--- a/frontend/js/leaderboard/render.js
+++ b/frontend/js/leaderboard/render.js
@@ -100,7 +100,7 @@ function createExternalIcon() {
function renderLeaderboardRow(user, rank) {
const rankTagEl = createRankTagElement(rank);
- const rankChangeEl = createRankChangeElement(user.rankChange);
+ const rankChangeEl = user.score > 0 ? createRankChangeElement(user.rankChange) : null;
const easyPoints = 1;
const mediumPoints = 3;
const hardPoints = 5;
@@ -136,7 +136,16 @@ function renderLeaderboardRow(user, rank) {
// Rank (numeric — safe)
const rankDiv = document.createElement("div");
rankDiv.className = "rank";
- rankDiv.textContent = rank;
+ if (rank === "") {
+ rankDiv.textContent = "[IDLE]";
+ rankDiv.style.color = "var(--text-dim)";
+ rankDiv.style.fontSize = "0.75rem";
+ } else if (rank === "--") {
+ rankDiv.textContent = "[--]";
+ rankDiv.style.color = "var(--text-dim)";
+ } else {
+ rankDiv.textContent = rank;
+ }
row.appendChild(rankDiv);
// Name cell — tag is safe DOM element, name is user-controlled (textContent)
@@ -260,7 +269,16 @@ function renderMobileCard(user, rank) {
const mobileRank = document.createElement("div");
mobileRank.className = "mobile-rank";
- mobileRank.textContent = `#${rank}`;
+ if (rank === "") {
+ mobileRank.textContent = "[IDLE]";
+ mobileRank.style.color = "var(--text-dim)";
+ mobileRank.style.fontSize = "0.75rem";
+ } else if (rank === "--") {
+ mobileRank.textContent = "[--]";
+ mobileRank.style.color = "var(--text-dim)";
+ } else {
+ mobileRank.textContent = `#${rank}`;
+ }
cardHeader.appendChild(mobileRank);
const mobileScore = document.createElement("div");
@@ -293,7 +311,7 @@ function renderMobileCard(user, rank) {
const mobileName = document.createElement("div");
mobileName.className = "mobile-name";
const mobileRankTagEl = createRankTagElement(rank);
- const mobileRankChangeEl = createRankChangeElement(user.rankChange);
+ const mobileRankChangeEl = user.score > 0 ? createRankChangeElement(user.rankChange) : null;
if (mobileRankTagEl) {
mobileName.appendChild(mobileRankTagEl);
}
diff --git a/frontend/leaderboard.html b/frontend/leaderboard.html
index ab852474..3c4345df 100644
--- a/frontend/leaderboard.html
+++ b/frontend/leaderboard.html
@@ -400,20 +400,37 @@
Leaderboard
countEl.textContent = "";
countEl.style.opacity = "0";
}
- document.getElementById("prev-page-btn").disabled = currentPage === 1;
+ const overallDataset =
+ window.leaderboardData && window.leaderboardData["overall"]
+ ? window.leaderboardData["overall"]
+ : originalData || [];
+
+ const zeroScoreUserIds = new Set(
+ overallDataset
+ .filter((user) => user && user.score === 0)
+ .map((user) => user.id),
+ );
+
+ const renderableData = filteredData.filter(
+ (user) => user && !zeroScoreUserIds.has(user.id),
+ );
+
+ const totalPages = Math.ceil(renderableData.length / itemsPerPage) || 1;
+ document.getElementById("prev-page-btn").disabled = currentPage === 1;
document.getElementById("next-page-btn").disabled =
- currentPage === Math.ceil(filteredData.length / itemsPerPage);
- const statsEl = document.getElementById("leaderboard-stats");
+ currentPage === totalPages;
- const totalPages = Math.ceil(filteredData.length / itemsPerPage);
+ const statsEl = document.getElementById("leaderboard-stats");
const startRow =
- filteredData.length === 0 ? 0 : (currentPage - 1) * itemsPerPage + 1;
+ renderableData.length === 0
+ ? 0
+ : (currentPage - 1) * itemsPerPage + 1;
const endRow = Math.min(
currentPage * itemsPerPage,
- filteredData.length,
+ renderableData.length,
);
statsEl.innerHTML = `
@@ -428,10 +445,11 @@ Leaderboard
| Page: ${currentPage}/${totalPages}
`;
- renderLeaderboard(filteredData);
+
+ renderLeaderboard(filteredData, zeroScoreUserIds, totalPages);
}
- function renderLeaderboard(data) {
+ function renderLeaderboard(data, zeroScoreUserIds, totalPages) {
const body = document.getElementById("leaderboard-body");
const mobileCards = document.getElementById("mobile-cards");
@@ -443,51 +461,80 @@ Leaderboard
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
- const displayData = isSearching
- ? data
- : data.slice(startIndex, endIndex);
-
- if (displayData.length === 0) {
- body.innerHTML = `
-
- [SYS]: NO_MATCHING_USERS_FOUND
-
- `;
-
- mobileCards.innerHTML = `
-
- [SYS]: NO_MATCHING_USERS_FOUND
-
- `;
+ const activeList = data.filter(
+ (user) => !zeroScoreUserIds.has(user.id),
+ );
+ const inactiveList = data.filter((user) =>
+ zeroScoreUserIds.has(user.id),
+ );
+
+ const displayActiveData = isSearching
+ ? activeList
+ : activeList.slice(startIndex, endIndex);
+
+ if (displayActiveData.length === 0 && inactiveList.length === 0) {
+ const emptyStateHtml = `[SYS]: NO_MATCHING_USERS_FOUND
`;
+ body.innerHTML = emptyStateHtml;
+ mobileCards.innerHTML = emptyStateHtml;
return;
}
- displayData.forEach((user, index) => {
- const rank =
+ displayActiveData.forEach((user, index) => {
+ let rank =
user.originalRank !== undefined
? user.originalRank
: startIndex + index + 1;
- const row = renderLeaderboardRow(user, rank);
- const card = renderMobileCard(user, rank);
- body.appendChild(row);
- mobileCards.appendChild(card);
- });
- renderPagination(data.length);
- }
- function setActiveTab(activeTab) {
- document.querySelectorAll(".tab").forEach((tab) => {
- tab.classList.remove("active");
- if (tab.dataset.tab === activeTab) {
- tab.classList.add("active");
+
+ if (activeDatasetType !== "overall" && user.score === 0) {
+ rank = "--";
}
+
+ body.appendChild(renderLeaderboardRow(user, rank));
+ mobileCards.appendChild(renderMobileCard(user, rank));
});
+ if (
+ inactiveList.length > 0 &&
+ (currentPage === totalPages || isSearching)
+ ) {
+ const dividerText = "── [ SECTION: NO ACTIVITY YET ] ──";
+
+ const tableHeader = document.createElement("div");
+ tableHeader.className = "no-results";
+ tableHeader.style.gridColumn = "1 / -1";
+ tableHeader.innerText = dividerText;
+ body.appendChild(tableHeader);
+
+ const mobileHeader = document.createElement("div");
+ mobileHeader.className = "no-results";
+ mobileHeader.innerText = dividerText;
+ mobileCards.appendChild(mobileHeader);
+
+ inactiveList.forEach((user) => {
+ body.appendChild(renderLeaderboardRow(user, ""));
+ mobileCards.appendChild(renderMobileCard(user, ""));
+ });
+ }
+
+ renderPagination(activeList.length);
+ }
+
+ function setActiveTab(activeTab) {
if (!window.leaderboardData[activeTab]) {
return;
}
activeDatasetType = activeTab;
currentPage = 1;
+
+ document.querySelectorAll(".tab").forEach((tab) => {
+ if (tab.dataset.tab === activeTab) {
+ tab.classList.add("active");
+ } else {
+ tab.classList.remove("active");
+ }
+ });
+
applyFiltersAndRender();
}
diff --git a/frontend/styles/main.css b/frontend/styles/main.css
index d9fcf7e8..b3a46993 100644
--- a/frontend/styles/main.css
+++ b/frontend/styles/main.css
@@ -1574,7 +1574,8 @@ body.crt-scrolling {
}
.leaderboard-header,
- .leaderboard-row {
+ .leaderboard-row,
+ .leaderboard .no-results {
display: none;
}
diff --git a/scripts/sync-leaderboard.js b/scripts/sync-leaderboard.js
index 105698e7..79073617 100644
--- a/scripts/sync-leaderboard.js
+++ b/scripts/sync-leaderboard.js
@@ -34,10 +34,14 @@ function getFileName(daysAgo) {
function assignCompetitionRanks(sortedData) {
let currentRank = 1;
for (let i = 0; i < sortedData.length; i++) {
- if (i > 0 && sortedData[i].score < sortedData[i - 1].score) {
- currentRank = i + 1;
+ if (sortedData[i].score === 0) {
+ sortedData[i].originalRank = "--";
+ } else {
+ if (i > 0 && sortedData[i].score < sortedData[i - 1].score) {
+ currentRank = i + 1;
+ }
+ sortedData[i].originalRank = currentRank;
}
- sortedData[i].originalRank = currentRank;
}
}