Add historical leaderboard tabs UI
This commit is contained in:
72
ai/tasks/done/TASK-038-historical-leaderboards-tabs-ui.md
Normal file
72
ai/tasks/done/TASK-038-historical-leaderboards-tabs-ui.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# TASK-038-historical-leaderboards-tabs-ui
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Transformar la sección de ranking semanal de la página histórica en un bloque con pestañas, permitiendo alternar entre varios leaderboards semanales del servidor seleccionado.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
La UI histórica actual muestra un único ranking semanal de kills. El objetivo ahora es convertir esa zona en una sección con pestañas para poder consultar varias métricas sin recargar la página y manteniendo una experiencia clara. Las pestañas requeridas inicialmente son:
|
||||||
|
- Top kills
|
||||||
|
- Top muertes
|
||||||
|
- Top número de partidas con más de 100 kills
|
||||||
|
- Top puntos de soporte
|
||||||
|
|
||||||
|
La UI debe seguir siendo propia del proyecto y consumir únicamente la API histórica interna.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
1. Revisar la página histórica actual y el bloque de ranking semanal.
|
||||||
|
2. Revisar la nueva API histórica multitétrica disponible tras la task previa.
|
||||||
|
3. Diseñar un sistema de pestañas claro y coherente con el estilo de la web.
|
||||||
|
4. Implementar las pestañas para:
|
||||||
|
- Top kills
|
||||||
|
- Top muertes
|
||||||
|
- Top número de partidas con más de 100 kills
|
||||||
|
- Top puntos de soporte
|
||||||
|
5. Hacer que las pestañas reutilicen el servidor actualmente seleccionado.
|
||||||
|
6. Mantener estados de:
|
||||||
|
- loading
|
||||||
|
- vacío
|
||||||
|
- error
|
||||||
|
7. Ajustar títulos, subtítulos y labels de la sección para que sean claros y no sobrecarguen visualmente.
|
||||||
|
8. No usar páginas externas ni incrustaciones del scoreboard de la comunidad.
|
||||||
|
9. Mantener la coherencia visual con el resto de la página histórica y con la landing principal.
|
||||||
|
10. Al completar la implementación:
|
||||||
|
- dejar el repositorio consistente
|
||||||
|
- hacer commit
|
||||||
|
- hacer push al remoto si el entorno lo permite
|
||||||
|
|
||||||
|
## Files to Read First
|
||||||
|
- AGENTS.md
|
||||||
|
- frontend/historico.html
|
||||||
|
- frontend/assets/js/historico.js
|
||||||
|
- frontend/assets/css/historico.css
|
||||||
|
- backend/app/routes.py
|
||||||
|
- backend/app/payloads.py
|
||||||
|
- backend/README.md
|
||||||
|
|
||||||
|
## Expected Files to Modify
|
||||||
|
- frontend/historico.html
|
||||||
|
- frontend/assets/js/historico.js
|
||||||
|
- frontend/assets/css/historico.css
|
||||||
|
- opcionalmente documentación mínima si hay un cambio visible relevante en la navegación histórica
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- No crear nuevas páginas basadas en URLs externas.
|
||||||
|
- No romper la UI histórica actual fuera del bloque de ranking.
|
||||||
|
- No introducir frameworks nuevos.
|
||||||
|
- No hacer cambios destructivos.
|
||||||
|
- Mantener la solución centrada en pestañas, claridad y consumo de nuestra API.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
- La sección de ranking semanal se convierte en un bloque con pestañas funcionales.
|
||||||
|
- Las pestañas muestran:
|
||||||
|
- Top kills
|
||||||
|
- Top muertes
|
||||||
|
- Top partidas con más de 100 kills
|
||||||
|
- Top puntos de soporte
|
||||||
|
- La UI consume únicamente la API interna del proyecto.
|
||||||
|
- El selector de servidor sigue funcionando con estas pestañas.
|
||||||
|
- Los cambios quedan committeados y se hace push al remoto si el entorno lo permite.
|
||||||
|
|
||||||
|
## Change Budget
|
||||||
|
- Preferir menos de 5 archivos modificados.
|
||||||
|
- Preferir menos de 220 líneas cambiadas.
|
||||||
@@ -100,6 +100,41 @@
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.historical-tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.historical-tab {
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border: 1px solid rgba(159, 168, 141, 0.2);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(13, 17, 12, 0.62);
|
||||||
|
color: var(--muted);
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
transform 160ms ease,
|
||||||
|
border-color 160ms ease,
|
||||||
|
background 160ms ease,
|
||||||
|
color 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.historical-tab:hover,
|
||||||
|
.historical-tab:focus-visible,
|
||||||
|
.historical-tab.is-active {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
border-color: rgba(210, 182, 118, 0.46);
|
||||||
|
background: linear-gradient(180deg, rgba(183, 201, 125, 0.16), rgba(89, 101, 58, 0.18));
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
.historical-summary-grid {
|
.historical-summary-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
|
||||||
@@ -256,6 +291,14 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.historical-tabs {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.historical-tab {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.historical-selector__button {
|
.historical-selector__button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,33 @@ const HISTORICAL_SERVER_SLUGS = Object.freeze([
|
|||||||
"comunidad-hispana-02",
|
"comunidad-hispana-02",
|
||||||
]);
|
]);
|
||||||
const DEFAULT_HISTORICAL_SERVER = HISTORICAL_SERVER_SLUGS[0];
|
const DEFAULT_HISTORICAL_SERVER = HISTORICAL_SERVER_SLUGS[0];
|
||||||
|
const LEADERBOARD_METRICS = Object.freeze([
|
||||||
|
{
|
||||||
|
key: "kills",
|
||||||
|
title: "Top kills de los ultimos 7 dias",
|
||||||
|
valueHeading: "Kills",
|
||||||
|
emptyMessage: "Sin datos historicos suficientes para mostrar el top de kills semanal.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "deaths",
|
||||||
|
title: "Top muertes de los ultimos 7 dias",
|
||||||
|
valueHeading: "Muertes",
|
||||||
|
emptyMessage: "Sin datos historicos suficientes para mostrar el top de muertes semanal.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "matches_over_100_kills",
|
||||||
|
title: "Top partidas con 100+ kills",
|
||||||
|
valueHeading: "Partidas 100+",
|
||||||
|
emptyMessage: "Ningun jugador ha registrado partidas de 100+ kills en esta ventana semanal.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "support",
|
||||||
|
title: "Top puntos de soporte de los ultimos 7 dias",
|
||||||
|
valueHeading: "Soporte",
|
||||||
|
emptyMessage: "Sin datos historicos suficientes para mostrar el top de soporte semanal.",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const DEFAULT_LEADERBOARD_METRIC = LEADERBOARD_METRICS[0].key;
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const backendBaseUrl =
|
const backendBaseUrl =
|
||||||
@@ -10,51 +37,72 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const selectorButtons = Array.from(
|
const selectorButtons = Array.from(
|
||||||
document.querySelectorAll("[data-server-slug]"),
|
document.querySelectorAll("[data-server-slug]"),
|
||||||
);
|
);
|
||||||
|
const leaderboardTabButtons = Array.from(
|
||||||
|
document.querySelectorAll("[data-leaderboard-metric]"),
|
||||||
|
);
|
||||||
const summaryNode = document.getElementById("historical-summary");
|
const summaryNode = document.getElementById("historical-summary");
|
||||||
const rangeNode = document.getElementById("historical-range");
|
const rangeNode = document.getElementById("historical-range");
|
||||||
const summaryNoteNode = document.getElementById("historical-summary-note");
|
const summaryNoteNode = document.getElementById("historical-summary-note");
|
||||||
const weeklyStateNode = document.getElementById("weekly-top-kills-state");
|
const weeklyTitleNode = document.getElementById("weekly-ranking-title");
|
||||||
const weeklyTableNode = document.getElementById("weekly-top-kills-table");
|
const weeklyStateNode = document.getElementById("weekly-leaderboard-state");
|
||||||
const weeklyBodyNode = document.getElementById("weekly-top-kills-body");
|
const weeklyTableNode = document.getElementById("weekly-leaderboard-table");
|
||||||
|
const weeklyBodyNode = document.getElementById("weekly-leaderboard-body");
|
||||||
|
const weeklyValueHeadingNode = document.getElementById("weekly-leaderboard-value-heading");
|
||||||
const weeklyWindowNoteNode = document.getElementById("weekly-window-note");
|
const weeklyWindowNoteNode = document.getElementById("weekly-window-note");
|
||||||
const recentStateNode = document.getElementById("recent-matches-state");
|
const recentStateNode = document.getElementById("recent-matches-state");
|
||||||
const recentListNode = document.getElementById("recent-matches-list");
|
const recentListNode = document.getElementById("recent-matches-list");
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
let activeServerSlug = normalizeServerSlug(params.get("server"));
|
let activeServerSlug = normalizeServerSlug(params.get("server"));
|
||||||
|
let activeLeaderboardMetric = normalizeLeaderboardMetric(params.get("metric"));
|
||||||
|
let refreshRequestId = 0;
|
||||||
|
|
||||||
const refreshHistoricalView = async () => {
|
const refreshHistoricalView = async () => {
|
||||||
|
const requestId = refreshRequestId + 1;
|
||||||
|
refreshRequestId = requestId;
|
||||||
|
const activeMetricConfig = getLeaderboardMetricConfig(activeLeaderboardMetric);
|
||||||
|
|
||||||
syncActiveButtons(selectorButtons, activeServerSlug);
|
syncActiveButtons(selectorButtons, activeServerSlug);
|
||||||
|
syncLeaderboardTabs(leaderboardTabButtons, activeLeaderboardMetric);
|
||||||
setRangeBadge(rangeNode, "Cargando rango temporal", false);
|
setRangeBadge(rangeNode, "Cargando rango temporal", false);
|
||||||
summaryNoteNode.textContent =
|
summaryNoteNode.textContent =
|
||||||
"Este bloque resume solo la cobertura ya registrada en la base local.";
|
"Este bloque resume solo la cobertura ya registrada en la base local.";
|
||||||
renderSummaryLoading(summaryNode);
|
renderSummaryLoading(summaryNode);
|
||||||
|
weeklyTitleNode.textContent = activeMetricConfig.title;
|
||||||
|
weeklyValueHeadingNode.textContent = activeMetricConfig.valueHeading;
|
||||||
weeklyWindowNoteNode.textContent = "Cargando ventana semanal...";
|
weeklyWindowNoteNode.textContent = "Cargando ventana semanal...";
|
||||||
setState(weeklyStateNode, "Cargando ranking semanal...");
|
setState(weeklyStateNode, "Cargando ranking semanal...");
|
||||||
setState(recentStateNode, "Cargando partidas recientes...");
|
setState(recentStateNode, "Cargando partidas recientes...");
|
||||||
weeklyTableNode.hidden = true;
|
weeklyTableNode.hidden = true;
|
||||||
recentListNode.innerHTML = "";
|
recentListNode.innerHTML = "";
|
||||||
|
|
||||||
const [summaryResult, topKillsResult, recentMatchesResult] =
|
const [summaryResult, leaderboardResult, recentMatchesResult] =
|
||||||
await Promise.allSettled([
|
await Promise.allSettled([
|
||||||
fetchJson(
|
fetchJson(
|
||||||
`${backendBaseUrl}/api/historical/server-summary?server=${encodeURIComponent(activeServerSlug)}`,
|
`${backendBaseUrl}/api/historical/server-summary?server=${encodeURIComponent(activeServerSlug)}`,
|
||||||
),
|
),
|
||||||
fetchJson(
|
fetchJson(
|
||||||
`${backendBaseUrl}/api/historical/weekly-top-kills?server=${encodeURIComponent(activeServerSlug)}&limit=10`,
|
`${backendBaseUrl}/api/historical/weekly-leaderboard?server=${encodeURIComponent(activeServerSlug)}&metric=${encodeURIComponent(activeLeaderboardMetric)}&limit=10`,
|
||||||
),
|
),
|
||||||
fetchJson(
|
fetchJson(
|
||||||
`${backendBaseUrl}/api/historical/recent-matches?server=${encodeURIComponent(activeServerSlug)}&limit=6`,
|
`${backendBaseUrl}/api/historical/recent-matches?server=${encodeURIComponent(activeServerSlug)}&limit=6`,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (requestId !== refreshRequestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
hydrateSummary(summaryResult, summaryNode, rangeNode, summaryNoteNode);
|
hydrateSummary(summaryResult, summaryNode, rangeNode, summaryNoteNode);
|
||||||
hydrateWeeklyTopKills(
|
hydrateWeeklyLeaderboard(
|
||||||
topKillsResult,
|
leaderboardResult,
|
||||||
weeklyStateNode,
|
weeklyStateNode,
|
||||||
weeklyTableNode,
|
weeklyTableNode,
|
||||||
weeklyBodyNode,
|
weeklyBodyNode,
|
||||||
|
weeklyTitleNode,
|
||||||
|
weeklyValueHeadingNode,
|
||||||
weeklyWindowNoteNode,
|
weeklyWindowNoteNode,
|
||||||
|
activeMetricConfig,
|
||||||
);
|
);
|
||||||
hydrateRecentMatches(recentMatchesResult, recentStateNode, recentListNode);
|
hydrateRecentMatches(recentMatchesResult, recentStateNode, recentListNode);
|
||||||
};
|
};
|
||||||
@@ -68,6 +116,22 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
activeServerSlug = nextServerSlug;
|
activeServerSlug = nextServerSlug;
|
||||||
params.set("server", activeServerSlug);
|
params.set("server", activeServerSlug);
|
||||||
|
params.set("metric", activeLeaderboardMetric);
|
||||||
|
window.history.replaceState({}, "", `?${params.toString()}`);
|
||||||
|
void refreshHistoricalView();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
leaderboardTabButtons.forEach((button) => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
const nextMetric = normalizeLeaderboardMetric(button.dataset.leaderboardMetric);
|
||||||
|
if (nextMetric === activeLeaderboardMetric) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeLeaderboardMetric = nextMetric;
|
||||||
|
params.set("server", activeServerSlug);
|
||||||
|
params.set("metric", activeLeaderboardMetric);
|
||||||
window.history.replaceState({}, "", `?${params.toString()}`);
|
window.history.replaceState({}, "", `?${params.toString()}`);
|
||||||
void refreshHistoricalView();
|
void refreshHistoricalView();
|
||||||
});
|
});
|
||||||
@@ -129,13 +193,18 @@ function hydrateSummary(result, summaryNode, rangeNode, noteNode) {
|
|||||||
].join("");
|
].join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function hydrateWeeklyTopKills(
|
function hydrateWeeklyLeaderboard(
|
||||||
result,
|
result,
|
||||||
stateNode,
|
stateNode,
|
||||||
tableNode,
|
tableNode,
|
||||||
bodyNode,
|
bodyNode,
|
||||||
|
titleNode,
|
||||||
|
valueHeadingNode,
|
||||||
noteNode,
|
noteNode,
|
||||||
|
metricConfig,
|
||||||
) {
|
) {
|
||||||
|
titleNode.textContent = metricConfig.title;
|
||||||
|
valueHeadingNode.textContent = metricConfig.valueHeading;
|
||||||
if (result.status !== "fulfilled") {
|
if (result.status !== "fulfilled") {
|
||||||
noteNode.textContent =
|
noteNode.textContent =
|
||||||
"El ranking usa solo partidas cerradas dentro de la ultima ventana semanal.";
|
"El ranking usa solo partidas cerradas dentro de la ultima ventana semanal.";
|
||||||
@@ -148,10 +217,7 @@ function hydrateWeeklyTopKills(
|
|||||||
noteNode.textContent = buildWeeklyWindowNote(payload);
|
noteNode.textContent = buildWeeklyWindowNote(payload);
|
||||||
const items = payload?.items;
|
const items = payload?.items;
|
||||||
if (!Array.isArray(items) || items.length === 0) {
|
if (!Array.isArray(items) || items.length === 0) {
|
||||||
setState(
|
setState(stateNode, metricConfig.emptyMessage);
|
||||||
stateNode,
|
|
||||||
"Sin datos historicos suficientes para construir el ranking semanal.",
|
|
||||||
);
|
|
||||||
tableNode.hidden = true;
|
tableNode.hidden = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -162,9 +228,8 @@ function hydrateWeeklyTopKills(
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="historical-table__position">#${escapeHtml(item.ranking_position)}</td>
|
<td class="historical-table__position">#${escapeHtml(item.ranking_position)}</td>
|
||||||
<td>${escapeHtml(item.player?.name || "Jugador no identificado")}</td>
|
<td>${escapeHtml(item.player?.name || "Jugador no identificado")}</td>
|
||||||
<td>${escapeHtml(formatNumber(item.weekly_kills))}</td>
|
<td>${escapeHtml(formatNumber(item.metric_value))}</td>
|
||||||
<td>${escapeHtml(formatNumber(item.matches_considered))}</td>
|
<td>${escapeHtml(formatNumber(item.matches_considered))}</td>
|
||||||
<td>${escapeHtml(item.server?.name || "Servidor no disponible")}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
@@ -264,6 +329,14 @@ function syncActiveButtons(buttons, activeServerSlug) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncLeaderboardTabs(buttons, activeMetric) {
|
||||||
|
buttons.forEach((button) => {
|
||||||
|
const isActive = button.dataset.leaderboardMetric === activeMetric;
|
||||||
|
button.classList.toggle("is-active", isActive);
|
||||||
|
button.setAttribute("aria-selected", String(isActive));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeServerSlug(rawValue) {
|
function normalizeServerSlug(rawValue) {
|
||||||
const normalized = typeof rawValue === "string" ? rawValue.trim() : "";
|
const normalized = typeof rawValue === "string" ? rawValue.trim() : "";
|
||||||
if (HISTORICAL_SERVER_SLUGS.includes(normalized)) {
|
if (HISTORICAL_SERVER_SLUGS.includes(normalized)) {
|
||||||
@@ -273,6 +346,22 @@ function normalizeServerSlug(rawValue) {
|
|||||||
return DEFAULT_HISTORICAL_SERVER;
|
return DEFAULT_HISTORICAL_SERVER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeLeaderboardMetric(rawValue) {
|
||||||
|
const normalized = typeof rawValue === "string" ? rawValue.trim() : "";
|
||||||
|
if (LEADERBOARD_METRICS.some((metric) => metric.key === normalized)) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_LEADERBOARD_METRIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLeaderboardMetricConfig(metricKey) {
|
||||||
|
return (
|
||||||
|
LEADERBOARD_METRICS.find((metric) => metric.key === metricKey) ||
|
||||||
|
LEADERBOARD_METRICS[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function buildRangeLabel(start, end) {
|
function buildRangeLabel(start, end) {
|
||||||
if (!start && !end) {
|
if (!start && !end) {
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -94,27 +94,69 @@
|
|||||||
<div class="panel__header historical-panel__header">
|
<div class="panel__header historical-panel__header">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow eyebrow--section">Ranking semanal</p>
|
<p class="eyebrow eyebrow--section">Ranking semanal</p>
|
||||||
<h2 id="weekly-ranking-title">Top kills de los ultimos 7 dias</h2>
|
<h2 id="weekly-ranking-title">Ranking semanal del servidor activo</h2>
|
||||||
<p class="historical-panel__note" id="weekly-window-note">
|
<p class="historical-panel__note" id="weekly-window-note">
|
||||||
Cargando ventana semanal...
|
Cargando ventana semanal...
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="historical-state" id="weekly-top-kills-state" aria-live="polite">
|
<div
|
||||||
|
class="historical-tabs"
|
||||||
|
id="weekly-leaderboard-tabs"
|
||||||
|
role="tablist"
|
||||||
|
aria-label="Metricas del ranking semanal"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="historical-tab is-active"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected="true"
|
||||||
|
data-leaderboard-metric="kills"
|
||||||
|
>
|
||||||
|
Top kills
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="historical-tab"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected="false"
|
||||||
|
data-leaderboard-metric="deaths"
|
||||||
|
>
|
||||||
|
Top muertes
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="historical-tab"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected="false"
|
||||||
|
data-leaderboard-metric="matches_over_100_kills"
|
||||||
|
>
|
||||||
|
Partidas 100+ kills
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="historical-tab"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected="false"
|
||||||
|
data-leaderboard-metric="support"
|
||||||
|
>
|
||||||
|
Soporte
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="historical-state" id="weekly-leaderboard-state" aria-live="polite">
|
||||||
Cargando ranking semanal...
|
Cargando ranking semanal...
|
||||||
</p>
|
</p>
|
||||||
<div class="historical-table-shell">
|
<div class="historical-table-shell">
|
||||||
<table class="historical-table" id="weekly-top-kills-table" hidden>
|
<table class="historical-table" id="weekly-leaderboard-table" hidden>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Pos.</th>
|
<th>Pos.</th>
|
||||||
<th>Jugador</th>
|
<th>Jugador</th>
|
||||||
<th>Kills</th>
|
<th id="weekly-leaderboard-value-heading">Kills</th>
|
||||||
<th>Partidas</th>
|
<th>Partidas</th>
|
||||||
<th>Servidor</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="weekly-top-kills-body"></tbody>
|
<tbody id="weekly-leaderboard-body"></tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user