From b64d4e180f1b8552a8606d6dcf483c662f851f90 Mon Sep 17 00:00:00 2001 From: devRaGonSa Date: Tue, 9 Jun 2026 13:05:33 +0200 Subject: [PATCH] Polish public page navigation and copy --- ...public-pages-navigation-and-copy-polish.md | 139 ++++++++++++++++++ frontend/assets/css/styles.css | 79 +++++++++- frontend/assets/js/ranking.js | 30 ++-- frontend/assets/js/stats.js | 41 +++--- frontend/historico.html | 43 +++--- frontend/index.html | 32 ++-- frontend/ranking.html | 76 +++++----- frontend/stats.html | 72 +++++---- 8 files changed, 381 insertions(+), 131 deletions(-) create mode 100644 ai/tasks/done/TASK-203-public-pages-navigation-and-copy-polish.md diff --git a/ai/tasks/done/TASK-203-public-pages-navigation-and-copy-polish.md b/ai/tasks/done/TASK-203-public-pages-navigation-and-copy-polish.md new file mode 100644 index 0000000..8f93c55 --- /dev/null +++ b/ai/tasks/done/TASK-203-public-pages-navigation-and-copy-polish.md @@ -0,0 +1,139 @@ +--- +id: TASK-203 +title: Public pages navigation and copy polish +status: done +type: frontend +team: Frontend Senior +supporting_teams: + - Experto en interfaz +roadmap_item: foundation +priority: medium +--- + +# TASK-203 - Public pages navigation and copy polish + +## Goal + +Reorganizar la navegacion superior y normalizar el layout visual de `index`, `historico`, `stats` y `ranking` para que compartan una navegacion publica coherente, una cabecera visual alineada y textos visibles corregidos en UTF-8. + +## Context + +Las paginas publicas principales ya exponen el producto, pero hoy mezclan botones sueltos dentro del hero, encabezados desalineados y varios textos mal codificados o demasiado tecnicos para usuarios normales. Esta tarea corrige solo la capa visual y de copy en `frontend/`, sin tocar backend, endpoints, logica de datos, Elo/MMR pausado, Comunidad Hispana #03 ni assets/SVGs de armas. + +Preserve the current product identity: Spanish-speaking HLL Vietnam community, military/Vietnam/tactical/sober visual direction and controlled repository evolution. + +## Steps + +1. Inspeccionar el HTML, CSS y JS compartidos de las paginas publicas afectadas. +2. Crear o reutilizar una navegacion superior global para `index`, `historico`, `stats` y `ranking`. +3. Alinear el hero de `stats` y `ranking` con la composicion visual usada por `index` y `historico`. +4. Limpiar botones duplicados dentro del hero y simplificar el copy visible sin cambiar logica de datos. +5. Corregir textos visibles con problemas de codificacion en las paginas afectadas y validar el resultado por inspeccion. + +## Files to Read First + +- `AGENTS.md` +- `ai/repo-context.md` +- `ai/architecture-index.md` +- `frontend/index.html` +- `frontend/historico.html` +- `frontend/assets/css/styles.css` + +## Expected Files to Modify + +- `ai/tasks/done/TASK-203-public-pages-navigation-and-copy-polish.md` +- `frontend/index.html` +- `frontend/historico.html` +- `frontend/stats.html` +- `frontend/ranking.html` +- `frontend/assets/css/styles.css` +- `frontend/assets/js/stats.js` +- `frontend/assets/js/ranking.js` + +## Constraints + +- Keep the change minimal. +- Preserve HLL Vietnam project identity. +- Do not introduce unnecessary frameworks or dependencies. +- Do not implement backend functionality unless the task explicitly requires it. +- Do not expand Elo/MMR, historical workers or RCON server #03 handling unless the task explicitly requires it. +- Do not overwrite repository-specific context with generic platform template text. +- No ejecutar `ai-platform run`. +- No modificar `backend/`, scripts de validacion backend, endpoints, logica de datos, assets/SVGs de armas ni siluetas. +- No reactivar Elo/MMR ni reintroducir Comunidad Hispana #03. + +## Validation + +Before completing the task ensure: + +- `index.html`, `historico.html`, `stats.html` y `ranking.html` muestran navegacion superior con acceso a Inicio, Historico, Stats y Ranking +- `index.html` mantiene `Unirse al Discord` dentro del hero +- `stats.html` y `ranking.html` muestran imagen/icono de comunidad en hero +- `stats.html` y `ranking.html` ya no muestran `Backend operativo` +- no aparecen textos mal codificados visibles como `Público`, `Públicos`, `Año`, `AÃo`, `Histórico` +- `ranking.html` ya no muestra `Fuente` en el panel visible de tabla activa +- `git diff --name-only` matches the expected scope +- no unrelated files were modified +- integration tests are run when relevant and configured + +## Outcome + +Archivos modificados: + +- `frontend/index.html` +- `frontend/historico.html` +- `frontend/stats.html` +- `frontend/ranking.html` +- `frontend/assets/css/styles.css` +- `frontend/assets/js/stats.js` +- `frontend/assets/js/ranking.js` +- `ai/tasks/done/TASK-203-public-pages-navigation-and-copy-polish.md` + +Navegacion nueva: + +- Se anadio una navegacion superior comun en las cuatro paginas publicas con acceso directo a Inicio, Historico, Stats y Ranking. +- El estado activo se marca por pagina y la navegacion queda separada del hero como banda superior de tabs tacticos. + +Textos corregidos: + +- Se corrigieron textos visibles relacionados con Historico, publico/publicos, estadisticas, metrica, limite y ano/anual. +- En HTML se usaron entidades para asegurar render correcto en entorno Windows y lectura segura en navegador. +- En JS visible se usaron escapes Unicode donde hacia falta para evitar mojibake. + +Botones movidos: + +- En `index`, `Stats`, `Ranking` y `Ver historico propio` salieron del hero y pasaron a la navegacion global. +- En `historico`, `Volver inicio` y `Stats` se sustituyeron por la navegacion global, y se anadio acceso directo a Ranking. +- En `stats` y `ranking`, los enlaces de retorno cruzado del hero se sustituyeron por la navegacion global. + +Elementos retirados: + +- Se elimino el badge visible de backend en `stats` y `ranking`. +- Se elimino el campo visual `Fuente` del panel visible de metadata en `ranking`. + +Validaciones ejecutadas: + +- Revision por inspeccion de `frontend/index.html`, `frontend/historico.html`, `frontend/stats.html` y `frontend/ranking.html`. +- Verificacion de navegacion superior comun en las cuatro paginas. +- Verificacion de que `Unirse al Discord` permanece en el hero de `index`. +- Verificacion de que `stats` y `ranking` incluyen `logo.png` en el hero. +- Verificacion de que ya no existen `id="stats-backend-state"` ni `id="ranking-backend-state"` en HTML. +- Verificacion de que `ranking.js` ya no genera la tarjeta visible `Fuente`. +- Revision de `git status --short --untracked-files=all` y `git diff --name-only`. +- Confirmacion de que no hay cambios en `backend/` ni en `scripts/`. + +Tests: + +- No se ejecutaron scripts de validacion frontend existentes porque los scripts disponibles no son ligeros para este alcance y uno de ellos (`scripts/run-stats-validation.ps1`) exige el chip `stats-backend-state`, eliminado intencionadamente por esta task. No se modifico ese script por estar fuera de alcance. + +Confirmaciones de alcance: + +- No se tocaron `backend/`, endpoints, logica de datos, scripts backend, Elo/MMR ni Comunidad Hispana #03. +- No se tocaron assets/SVGs de armas. Los cambios ya presentes en `frontend/assets/img/weapons/` y `ai/system-metrics.md` eran previos y se dejaron intactos. + +## Change Budget + +- Prefer fewer than 5 modified files. +- Prefer changes under 200 lines when feasible. +- Split the work into follow-up tasks if limits are exceeded. + diff --git a/frontend/assets/css/styles.css b/frontend/assets/css/styles.css index f029576..7ca37da 100644 --- a/frontend/assets/css/styles.css +++ b/frontend/assets/css/styles.css @@ -1,4 +1,4 @@ -:root { +:root { --bg: #0f120d; --bg-deep: #090b08; --bg-elevated: rgba(27, 33, 24, 0.92); @@ -85,6 +85,58 @@ a { pointer-events: none; } +.public-nav { + width: min(var(--page-shell-width), calc(100vw - (var(--page-shell-gutter) * 2))); + margin: 0 auto 16px; + padding: 10px; + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; + border: 1px solid var(--border); + border-radius: 22px; + background: + linear-gradient(180deg, rgba(24, 30, 22, 0.9), rgba(10, 13, 9, 0.96)), + radial-gradient(circle at top center, rgba(183, 201, 125, 0.08), transparent 40%); + box-shadow: var(--shadow-soft); +} + +.public-nav__link { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 50px; + padding: 0 18px; + border: 1px solid rgba(159, 168, 141, 0.22); + border-radius: 999px; + background: linear-gradient(180deg, rgba(18, 23, 16, 0.88), rgba(10, 12, 9, 0.96)); + color: var(--text-soft); + font-size: 0.82rem; + font-weight: 800; + letter-spacing: 0.12em; + text-transform: uppercase; + transition: + transform 160ms ease, + border-color 160ms ease, + background 160ms ease, + color 160ms ease, + box-shadow 160ms ease; +} + +.public-nav__link:hover, +.public-nav__link:focus-visible { + transform: translateY(-1px); + border-color: rgba(210, 182, 118, 0.48); + background: linear-gradient(180deg, rgba(43, 50, 36, 0.94), rgba(16, 20, 14, 0.98)); + color: var(--text); +} + +.public-nav__link.is-active { + border-color: rgba(183, 201, 125, 0.42); + background: linear-gradient(180deg, rgba(183, 201, 125, 0.22), rgba(89, 101, 58, 0.28)); + color: var(--text); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); +} + .hero { position: relative; overflow: hidden; @@ -920,6 +972,11 @@ h2 { margin-top: 8px; } +.stats-hero .hero__brand, +.ranking-hero .hero__brand { + align-items: center; +} + .stats-search-form { margin: 0 0 14px; display: grid; @@ -1401,6 +1458,10 @@ h2 { } @media (max-width: 760px) { + .public-nav { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .servers-grid, .servers-grid--section, .clans-grid { @@ -1430,6 +1491,18 @@ h2 { font-size: 0.98rem; } + .public-nav { + padding: 8px; + gap: 8px; + } + + .public-nav__link { + min-height: 46px; + padding-inline: 12px; + font-size: 0.74rem; + letter-spacing: 0.08em; + } + .stats-search-form__fields { grid-template-columns: 1fr; } @@ -1475,7 +1548,8 @@ h2 { width: 100%; } - .secondary-button { + .secondary-button, + .public-nav__link { width: 100%; } @@ -1541,3 +1615,4 @@ h2 { grid-template-columns: 1fr; } } + diff --git a/frontend/assets/js/ranking.js b/frontend/assets/js/ranking.js index 04ae61b..0d3ebd2 100644 --- a/frontend/assets/js/ranking.js +++ b/frontend/assets/js/ranking.js @@ -1,4 +1,4 @@ -document.addEventListener("DOMContentLoaded", () => { +document.addEventListener("DOMContentLoaded", () => { const backendBaseUrl = document.body.dataset.backendBaseUrl || "http://127.0.0.1:8000"; const form = document.getElementById("ranking-form"); const timeframeSelect = document.getElementById("ranking-timeframe"); @@ -185,7 +185,7 @@ document.addEventListener("DOMContentLoaded", () => { if (isAnnual) { setFilterNote( - "El ranking anual sigue limitado a kills porque solo esa metrica tiene lectura snapshot segura.", + "El ranking anual sigue limitado a kills porque solo esa m\u00e9trica tiene lectura snapshot segura.", "warning", ); return; @@ -272,7 +272,7 @@ document.addEventListener("DOMContentLoaded", () => { if (!isBackendOnline) { setRankingState("error", "Backend no disponible. El ranking queda en estado offline."); renderEmptyState( - "No fue posible contactar el backend. Reintenta cuando el servicio vuelva a estar operativo.", + "No fue posible contactar el backend. Reintenta cuando el servicio vuelva a estar disponible.", ); return; } @@ -281,8 +281,8 @@ document.addEventListener("DOMContentLoaded", () => { if (timeframe === "annual") { year = Number.parseInt(String(yearInput?.value || "").trim(), 10); if (!Number.isFinite(year) || year <= 0) { - setRankingState("error", "El ano solicitado no es valido."); - renderEmptyState("Corrige el ano y vuelve a consultar el ranking anual."); + setRankingState("error", "El a\u00f1o solicitado no es v\u00e1lido."); + renderEmptyState("Corrige el a\u00f1o y vuelve a consultar el ranking anual."); return; } } @@ -322,7 +322,7 @@ document.addEventListener("DOMContentLoaded", () => { setBackendState("Backend no disponible", false); setRankingState("error", "Error controlado al cargar el ranking."); renderEmptyState( - "La lectura del ranking fallo en este intento. Revisa el backend o actualiza la pagina.", + "La lectura del ranking fall\u00f3 en este intento. Revisa el backend o actualiza la p\u00e1gina.", ); } } @@ -343,20 +343,20 @@ document.addEventListener("DOMContentLoaded", () => { ) { setRankingState("warning", "El ranking anual solo admite kills por ahora."); renderEmptyState( - "Las metricas extra estan disponibles en semanal y mensual. El ranking anual sigue limitado a kills mientras no existan snapshots seguros adicionales.", + "Las m\u00e9tricas extra est\u00e1n disponibles en semanal y mensual. El ranking anual sigue limitado a kills mientras no existan snapshots seguros adicionales.", ); return; } if (statusCode === 400 && normalizedMessage.includes("metric")) { - setRankingState("warning", "La metrica solicitada no esta soportada."); + setRankingState("warning", "La m\u00e9trica solicitada no est\u00e1 soportada."); renderEmptyState( "Usa kills, deaths, teamkills, partidas jugadas, K/D o kills por partida.", ); return; } if (statusCode === 400 && normalizedMessage.includes("year")) { - setRankingState("warning", "El ranking anual requiere un ano valido."); - renderEmptyState("Define un ano valido para consultar la lectura anual."); + setRankingState("warning", "El ranking anual requiere un a\u00f1o v\u00e1lido."); + renderEmptyState("Define un a\u00f1o v\u00e1lido para consultar la lectura anual."); return; } if (statusCode === 400 && normalizedMessage.includes("timeframe")) { @@ -398,7 +398,7 @@ document.addEventListener("DOMContentLoaded", () => { if (timeframe === "annual" && snapshotStatus === "missing") { setRankingState("warning", "El snapshot anual solicitado aun no fue generado."); renderEmptyState( - "No existe snapshot anual para el ano y servidor elegidos. Este estado es informativo y no implica caida del backend.", + "No existe snapshot anual para el a\u00f1o y servidor elegidos. Este estado es informativo y no implica ca\u00edda del backend.", ); return; } @@ -441,10 +441,9 @@ document.addEventListener("DOMContentLoaded", () => { const cards = [ { label: "Periodo activo", value: labelForTimeframe(timeframe), active: true }, { label: "Servidor activo", value: labelForServer(data.server_id), active: true }, - { label: "Metrica activa", value: labelForMetric(metric), active: true }, - { label: "Limite", value: `Top ${safeInt(data.limit, safeInt(defaultLimit, 20))}` }, + { label: "M\u00e9trica activa", value: labelForMetric(metric), active: true }, + { label: "L\u00edmite", value: `Top ${safeInt(data.limit, safeInt(defaultLimit, 20))}` }, { label: "Ventana", value: labelForWindow(data) }, - { label: "Fuente", value: String(source.read_model || "No disponible") }, { label: "Actualizado", value: formatDateTime(source.generated_at) }, ]; @@ -490,7 +489,7 @@ document.addEventListener("DOMContentLoaded", () => { function labelForWindow(data) { if (String(data.timeframe || "") === "annual") { - return `Ano ${safeInt(data.year, currentYear)}`; + return `A\u00f1o ${safeInt(data.year, currentYear)}`; } return String(data.window_label || data.window_kind || "Ventana activa"); } @@ -586,3 +585,4 @@ document.addEventListener("DOMContentLoaded", () => { .replaceAll("'", "'"); } }); + diff --git a/frontend/assets/js/stats.js b/frontend/assets/js/stats.js index b4ca6d3..4fdb3ce 100644 --- a/frontend/assets/js/stats.js +++ b/frontend/assets/js/stats.js @@ -1,4 +1,4 @@ -document.addEventListener("DOMContentLoaded", () => { +document.addEventListener("DOMContentLoaded", () => { const backendBaseUrl = document.body.dataset.backendBaseUrl || "http://127.0.0.1:8000"; const searchForm = document.getElementById("stats-search-form"); const searchInput = document.getElementById("stats-search-input"); @@ -39,22 +39,22 @@ document.addEventListener("DOMContentLoaded", () => { "Error al buscar jugadores. Verifica backend y reintenta.", searchReadyPrefix: "Se encontraron ", searchReadySuffix: - " jugador(es). Selecciona uno para ver sus estadisticas.", + " jugador(es). Selecciona uno para ver sus estad\u00edsticas.", searchShortQueryHelp: - "Usa al menos 1 caracter para iniciar la busqueda.", - profileLoading: "Cargando estadisticas personales...", + "Usa al menos 1 car\u00e1cter para iniciar la b\u00fasqueda.", + profileLoading: "Cargando estad\u00edsticas personales...", profileNoStats: - "Jugador sin estadisticas suficientes para mostrar datos personales.", - profileError: "No fue posible cargar las estadisticas del jugador.", + "Jugador sin estad\u00edsticas suficientes para mostrar datos personales.", + profileError: "No fue posible cargar las estad\u00edsticas del jugador.", annualLoading: "Cargando ranking anual...", annualMissing: - "No hay snapshot generado para el ano solicitado. Mostrara estado missing hasta que exista.", + "No hay snapshot generado para el a\u00f1o solicitado. Mostrar\u00e1 estado pending hasta que exista.", annualReadyEmpty: - "Snapshot anual listo pero sin datos para el ano y servidor seleccionado.", + "Ranking anual listo, pero sin datos para el a\u00f1o y servidor seleccionados.", annualUnsupportedMetric: - "La metrica anual solicitada no esta soportada en V1.", + "La m\u00e9trica anual solicitada no est\u00e1 soportada en V1.", annualMetricInvalid: - "Parametro de ranking anual invalido. Usa metric=kills en V1.", + "Par\u00e1metro de ranking anual inv\u00e1lido. Usa metric=kills en V1.", annualReadyPrefix: "Ranking anual listo para", weeklyPlaceholder: "Sin datos semanales. El ranking semanal se actualiza al cargar un jugador.", @@ -69,7 +69,7 @@ document.addEventListener("DOMContentLoaded", () => { monthlyWindowUnavailableSummary: "Resumen mensual no disponible temporalmente.", profileReadyTitle: "Perfil personal", - profileEmptyTitle: "Selecciona un jugador para ver sus estadisticas.", + profileEmptyTitle: "Selecciona un jugador para ver sus estad\u00edsticas.", partialProfileLoadWarning: "Algunos bloques de ranking no se cargaron; se mantienen los disponibles.", }; @@ -78,8 +78,8 @@ document.addEventListener("DOMContentLoaded", () => { if (searchHelpNode) { searchHelpNode.textContent = - "Usa el buscador para encontrar un jugador. Al seleccionar uno veras resumen semanal y mensual. " + - "El ranking anual se consulta por separado con el ano indicado."; + "Usa el buscador para encontrar un jugador. Al seleccionar uno ver\u00e1s resumen semanal y mensual. " + + "El ranking anual se consulta por separado con el a\u00f1o indicado."; } setBackendState(messages.backendChecking, false); @@ -176,7 +176,7 @@ document.addEventListener("DOMContentLoaded", () => { setBackendState(messages.backendOnline, true); setAnnualState( "neutral", - "Backend disponible. Selecciona un ano para cargar el ranking anual (kills).", + "Backend disponible. Selecciona un a\u00f1o para cargar el ranking anual (kills).", ); void loadAnnualRanking(); } catch (error) { @@ -252,7 +252,7 @@ document.addEventListener("DOMContentLoaded", () => { const year = resolveAnnualYear(); if (year === null) { - setAnnualState("error", "El ano ingresado no es valido."); + setAnnualState("error", "El a\u00f1o ingresado no es v\u00e1lido."); return; } @@ -345,7 +345,7 @@ document.addEventListener("DOMContentLoaded", () => { setAnnualState( "neutral", - `${messages.annualReadyPrefix} ${serverId}, ano ${year}, metrica ${metric}`, + `${messages.annualReadyPrefix} ${serverId}, a\u00f1o ${year}, m\u00e9trica ${metric}`, ); annualContentNode.innerHTML = ` @@ -353,7 +353,7 @@ document.addEventListener("DOMContentLoaded", () => {

Top ${limit} anual

Servidor: ${escapeHtml(serverId)}

-

Metrica: ${escapeHtml(metric)}

+

M\u00e9trica: ${escapeHtml(metric)}

Partidas fuente: ${safeInt(sourceMatches, 0)}

Actualizado: ${escapeHtml(generatedAt || "No disponible")}

@@ -755,8 +755,8 @@ document.addEventListener("DOMContentLoaded", () => { const metric = escapeHtml(String(ranking.metric || annualMetric)); return ` -

Posicion: ${rank}

-

Metric: ${metric}

+

Posici\u00f3n: ${rank}

+

M\u00e9trica: ${metric}

${escapeHtml(formatWindowRange(ranking))}

`; } @@ -783,7 +783,7 @@ document.addEventListener("DOMContentLoaded", () => { if (Number.isFinite(safeParseNumber(ranking.ranking_position))) { return { tone: "ok", - title: `Posicion #${safeInt(ranking.ranking_position, 0)}`, + title: `Posici\u00f3n #${safeInt(ranking.ranking_position, 0)}`, detail: `Jugador posicionado en el ranking ${label} activo por kills.`, }; } @@ -902,3 +902,4 @@ document.addEventListener("DOMContentLoaded", () => { .replaceAll("'", "'"); } }); + diff --git a/frontend/historico.html b/frontend/historico.html index df49bab..a5e2aca 100644 --- a/frontend/historico.html +++ b/frontend/historico.html @@ -1,30 +1,35 @@ - + - Historico - HLL Vietnam + Histórico - HLL Vietnam
+
-
- - VOLVER INICIO - - - STATS - -

Historico propio

-
+

Histórico propio

Registro de la Comunidad Hispana @@ -47,8 +53,8 @@ de nuestros servidores.

-
-
@@ -106,7 +112,7 @@
-

Rankings historicos

+

Rankings históricos

Ranking del alcance activo

Cargando rango del ranking... @@ -144,7 +150,7 @@ class="historical-tabs" id="weekly-leaderboard-tabs" role="tablist" - aria-label="Metricas del ranking" + aria-label="Métricas del ranking" > @@ -159,3 +168,4 @@ + diff --git a/frontend/stats.html b/frontend/stats.html index 00a81aa..809dd4b 100644 --- a/frontend/stats.html +++ b/frontend/stats.html @@ -1,43 +1,54 @@ - + Stats - HLL Vietnam

+
-
-

Seccion Stats

-

- Estadisticas - Personales -

-

- Busca un jugador por nombre o ID y revisa sus resultados semanales, mensuales - y ranking anual con base en datos materializados. -

-

- Comprobando disponibilidad del backend -

-
- - Volver al inicio - - - Ranking - - - Historico - +
+
+ Logo oficial de la comunidad HLL Vietnam +
+
+

Sección Stats

+

+ Estadísticas + Personales +

+

+ Busca un jugador por nombre o ID y revisa sus resultados semanales, mensuales y ranking anual. +

@@ -51,7 +62,7 @@

Buscar por nombre o ID

- Usa al menos 1 caracter para buscar. Al seleccionar un jugador se cargan sus + Usa al menos 1 carácter para buscar. Al seleccionar un jugador se cargarán sus stats y ranking semanal y mensual. El ranking anual se consulta por separado.

@@ -66,7 +77,7 @@ name="query" placeholder="Ej: Rambo o 76561198000000000" autocomplete="off" - aria-label="Texto de busqueda de jugadores" + aria-label="Texto de búsqueda de jugadores" />

- Esperando ano para cargar el ranking anual (Top 20 por kills). + Esperando año para cargar el ranking anual (Top 20 por kills).

@@ -147,3 +158,4 @@ +