fix: refine two-column live combat feed

This commit is contained in:
devRaGonSa
2026-05-22 08:58:50 +02:00
parent 95b5dc4dfc
commit 22c22ce02e
2 changed files with 150 additions and 38 deletions

View File

@@ -591,16 +591,16 @@
.current-match-killfeed-screen { .current-match-killfeed-screen {
position: relative; position: relative;
width: min(100%, 760px); width: 100%;
min-height: 180px; min-height: 180px;
max-height: 520px; max-height: 520px;
padding: 10px; padding: 10px;
overflow: hidden; overflow: hidden;
border: 1px solid rgba(183, 201, 125, 0.28); border: 1px solid rgba(159, 168, 141, 0.2);
border-radius: 6px; border-radius: 6px;
background: background:
linear-gradient(180deg, rgba(183, 201, 125, 0.08), transparent 24%), linear-gradient(180deg, rgba(255, 255, 255, 0.035), transparent 24%),
rgba(5, 8, 6, 0.92); rgba(7, 9, 9, 0.94);
box-shadow: box-shadow:
inset 0 0 0 1px rgba(0, 0, 0, 0.44), inset 0 0 0 1px rgba(0, 0, 0, 0.44),
inset 0 18px 48px rgba(0, 0, 0, 0.24); inset 0 18px 48px rgba(0, 0, 0, 0.24);
@@ -626,34 +626,41 @@
position: relative; position: relative;
z-index: 2; z-index: 2;
display: grid; display: grid;
gap: 6px; grid-template-columns: repeat(2, minmax(0, 1fr));
align-items: start;
gap: 8px;
max-height: 500px; max-height: 500px;
overflow: hidden; overflow: hidden;
} }
.current-match-killfeed__column {
display: grid;
align-content: start;
gap: 6px;
min-width: 0;
}
.current-match-killfeed__row { .current-match-killfeed__row {
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) minmax(92px, 124px) minmax(0, 1fr); grid-template-columns: minmax(0, 1fr) minmax(86px, 112px) minmax(0, 1fr);
align-items: center; align-items: center;
gap: 8px; gap: 8px;
min-height: 48px; min-height: 54px;
padding: 6px 9px; padding: 6px 9px;
overflow: hidden; overflow: hidden;
border: 1px solid rgba(159, 168, 141, 0.12); border: 1px solid rgba(159, 168, 141, 0.14);
border-left: 2px solid rgba(183, 201, 125, 0.46); border-left: 2px solid rgba(159, 168, 141, 0.24);
border-radius: 3px; border-radius: 3px;
color: var(--text); color: var(--text);
background: background: rgba(19, 22, 22, 0.78);
linear-gradient(90deg, rgba(183, 201, 125, 0.07), transparent 40%),
rgba(15, 20, 13, 0.68);
animation: current-match-killfeed-enter 180ms ease-out; animation: current-match-killfeed-enter 180ms ease-out;
} }
.current-match-killfeed__row:first-child { .current-match-killfeed__column:last-child .current-match-killfeed__row:last-child {
border-color: rgba(183, 201, 125, 0.28); border-color: rgba(210, 182, 118, 0.22);
background: background:
linear-gradient(90deg, rgba(183, 201, 125, 0.14), transparent 46%), linear-gradient(90deg, rgba(210, 182, 118, 0.08), transparent 46%),
rgba(20, 27, 17, 0.86); rgba(24, 25, 22, 0.92);
} }
.current-match-killfeed__row.is-teamkill { .current-match-killfeed__row.is-teamkill {
@@ -664,22 +671,36 @@
} }
.current-match-killfeed__player { .current-match-killfeed__player {
display: grid;
align-content: center;
gap: 3px;
min-width: 0; min-width: 0;
overflow: hidden;
font-size: 0.88rem; font-size: 0.88rem;
line-height: 1.2; line-height: 1.2;
}
.current-match-killfeed__player-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.current-match-killfeed__player--killer { .current-match-killfeed__player--killer {
color: var(--accent-strong); color: var(--text);
} }
.current-match-killfeed__player--victim { .current-match-killfeed__player--victim {
color: var(--text-soft); color: var(--text-soft);
} }
.current-match-killfeed__player-meta {
display: flex;
align-items: center;
gap: 4px;
min-width: 0;
}
.current-match-killfeed__weapon { .current-match-killfeed__weapon {
display: grid; display: grid;
grid-template-rows: 25px auto; grid-template-rows: 25px auto;
@@ -689,10 +710,10 @@
min-width: 92px; min-width: 92px;
min-height: 38px; min-height: 38px;
padding: 2px 5px; padding: 2px 5px;
border: 1px solid rgba(183, 201, 125, 0.18); border: 1px solid rgba(159, 168, 141, 0.18);
border-radius: 3px; border-radius: 3px;
color: var(--text); color: var(--text);
background: rgba(7, 10, 7, 0.72); background: rgba(8, 10, 11, 0.74);
} }
.current-match-killfeed__weapon-icon { .current-match-killfeed__weapon-icon {
@@ -733,19 +754,59 @@
white-space: nowrap; white-space: nowrap;
} }
.current-match-killfeed__team-badge,
.current-match-killfeed__teamkill { .current-match-killfeed__teamkill {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
margin-left: 4px; justify-content: center;
min-height: 20px; min-height: 18px;
padding: 0 5px; padding: 0 5px;
border: 1px solid rgba(210, 182, 118, 0.36);
border-radius: 4px; border-radius: 4px;
color: var(--accent-warm);
font-size: 0.62rem; font-size: 0.62rem;
font-weight: 800; font-weight: 800;
} }
.current-match-killfeed__team-badge {
border: 1px solid rgba(159, 168, 141, 0.2);
color: var(--text-soft);
background: rgba(159, 168, 141, 0.08);
}
.current-match-killfeed__team-badge--allies {
border-color: rgba(100, 139, 178, 0.34);
color: rgba(192, 215, 238, 0.96);
background: rgba(67, 101, 137, 0.28);
}
.current-match-killfeed__team-badge--axis {
border-color: rgba(167, 109, 96, 0.36);
color: rgba(231, 198, 190, 0.96);
background: rgba(116, 68, 58, 0.3);
}
.current-match-killfeed__team-badge--unknown {
border-color: rgba(159, 168, 141, 0.22);
color: var(--muted);
background: rgba(159, 168, 141, 0.1);
}
.current-match-killfeed__teamkill {
border: 1px solid rgba(210, 182, 118, 0.36);
color: var(--accent-warm);
}
.current-match-player-count {
width: fit-content;
margin: -6px 0 16px;
padding: 7px 10px;
border: 1px solid rgba(159, 168, 141, 0.16);
border-radius: 6px;
color: var(--text-soft);
background: rgba(14, 17, 18, 0.7);
font-size: 0.8rem;
font-weight: 700;
}
@keyframes current-match-killfeed-enter { @keyframes current-match-killfeed-enter {
from { from {
opacity: 0; opacity: 0;
@@ -977,6 +1038,7 @@
} }
.current-match-killfeed { .current-match-killfeed {
grid-template-columns: 1fr;
max-height: 500px; max-height: 500px;
} }
@@ -997,7 +1059,7 @@
padding-inline: 3px; padding-inline: 3px;
} }
.current-match-killfeed__player { .current-match-killfeed__player-name {
display: -webkit-box; display: -webkit-box;
overflow: hidden; overflow: hidden;
font-size: 0.78rem; font-size: 0.78rem;

View File

@@ -9,7 +9,7 @@ const CURRENT_MATCH_SCOREBOARDS = Object.freeze({
"comunidad-hispana-01": "https://scoreboard.comunidadhll.es", "comunidad-hispana-01": "https://scoreboard.comunidadhll.es",
"comunidad-hispana-02": "https://scoreboard.comunidadhll.es:5443", "comunidad-hispana-02": "https://scoreboard.comunidadhll.es:5443",
}); });
const CURRENT_MATCH_KILL_FEED_LIMIT = 15; const CURRENT_MATCH_KILL_FEED_LIMIT = 18;
const CURRENT_MATCH_WEAPONS = Object.freeze({ const CURRENT_MATCH_WEAPONS = Object.freeze({
"m1 garand": { label: "M1 Garand", icon: "m1_garand.PNG" }, "m1 garand": { label: "M1 Garand", icon: "m1_garand.PNG" },
mp40: { label: "MP40", icon: "mp40.PNG" }, mp40: { label: "MP40", icon: "mp40.PNG" },
@@ -225,11 +225,15 @@ function initializePlayerStats(nodes) {
<p class="historical-state" id="current-match-player-stats-state" aria-live="polite"> <p class="historical-state" id="current-match-player-stats-state" aria-live="polite">
Cargando estadisticas en vivo... Cargando estadisticas en vivo...
</p> </p>
<p class="current-match-player-count" id="current-match-player-count">
Jugadores detectados: 0
</p>
<div class="historical-table-shell" id="current-match-player-stats-shell" hidden></div> <div class="historical-table-shell" id="current-match-player-stats-shell" hidden></div>
`, `,
); );
} }
nodes.playerStatsState = document.getElementById("current-match-player-stats-state"); nodes.playerStatsState = document.getElementById("current-match-player-stats-state");
nodes.playerCount = document.getElementById("current-match-player-count");
nodes.playerStatsShell = document.getElementById("current-match-player-stats-shell"); nodes.playerStatsShell = document.getElementById("current-match-player-stats-shell");
return { return {
visibleSignature: "", visibleSignature: "",
@@ -249,9 +253,9 @@ function renderKillFeed(data, nodes, state) {
}); });
const events = [...state.byId.values()] const events = [...state.byId.values()]
.sort(compareKillFeedEvents) .sort(compareKillFeedEvents)
.slice(0, CURRENT_MATCH_KILL_FEED_LIMIT); .slice(-CURRENT_MATCH_KILL_FEED_LIMIT);
state.byId = new Map(events.map((event) => [event.event_id, event])); state.byId = new Map(events.map((event) => [event.event_id, event]));
state.latestEventId = events[0]?.event_id || state.latestEventId; state.latestEventId = events[events.length - 1]?.event_id || state.latestEventId;
if (events.length === 0) { if (events.length === 0) {
nodes.feedList.innerHTML = ""; nodes.feedList.innerHTML = "";
state.visibleSignature = ""; state.visibleSignature = "";
@@ -261,7 +265,7 @@ function renderKillFeed(data, nodes, state) {
const visualEvents = events; const visualEvents = events;
const visibleSignature = visualEvents.map((event) => event.event_id).join("|"); const visibleSignature = visualEvents.map((event) => event.event_id).join("|");
if (visibleSignature !== state.visibleSignature) { if (visibleSignature !== state.visibleSignature) {
nodes.feedList.innerHTML = visualEvents.map(renderKillFeedRow).join(""); nodes.feedList.innerHTML = renderKillFeedColumns(visualEvents);
state.visibleSignature = visibleSignature; state.visibleSignature = visibleSignature;
} }
nodes.feedState.textContent = formatKillFeedCoverage(data.scope); nodes.feedState.textContent = formatKillFeedCoverage(data.scope);
@@ -269,16 +273,34 @@ function renderKillFeed(data, nodes, state) {
} }
function compareKillFeedEvents(left, right) { function compareKillFeedEvents(left, right) {
const rightTime = Number(right.server_time);
const leftTime = Number(left.server_time); const leftTime = Number(left.server_time);
if (Number.isFinite(rightTime) && Number.isFinite(leftTime) && rightTime !== leftTime) { const rightTime = Number(right.server_time);
return rightTime - leftTime; if (Number.isFinite(leftTime) && Number.isFinite(rightTime) && leftTime !== rightTime) {
return leftTime - rightTime;
} }
return String(right.event_timestamp || "").localeCompare(String(left.event_timestamp || "")); return (
String(left.event_timestamp || "").localeCompare(String(right.event_timestamp || "")) ||
String(left.event_id || "").localeCompare(String(right.event_id || ""))
);
}
function renderKillFeedColumns(events) {
const splitIndex = Math.ceil(events.length / 2);
return [events.slice(0, splitIndex), events.slice(splitIndex)]
.map(
(columnEvents) => `
<div class="current-match-killfeed__column">
${columnEvents.map(renderKillFeedRow).join("")}
</div>
`,
)
.join("");
} }
function renderKillFeedRow(event) { function renderKillFeedRow(event) {
const weapon = resolveKillFeedWeapon(event.weapon); const weapon = resolveKillFeedWeapon(event.weapon);
const killerTeam = getKillFeedTeamDisplay(event.killer_team);
const victimTeam = getKillFeedTeamDisplay(event.victim_team);
const teamkillBadge = event.is_teamkill const teamkillBadge = event.is_teamkill
? '<span class="current-match-killfeed__teamkill">TK</span>' ? '<span class="current-match-killfeed__teamkill">TK</span>'
: ""; : "";
@@ -287,10 +309,15 @@ function renderKillFeedRow(event) {
class="current-match-killfeed__row${event.is_teamkill ? " is-teamkill" : ""}" class="current-match-killfeed__row${event.is_teamkill ? " is-teamkill" : ""}"
data-event-id="${escapeHtml(event.event_id || "")}" data-event-id="${escapeHtml(event.event_id || "")}"
> >
<strong class="current-match-killfeed__player current-match-killfeed__player--killer" title="${escapeHtml(event.killer_name || "Jugador no disponible")}"> <span class="current-match-killfeed__player current-match-killfeed__player--killer">
${escapeHtml(event.killer_name || "Jugador no disponible")} <strong class="current-match-killfeed__player-name" title="${escapeHtml(event.killer_name || "Jugador no disponible")}">
${teamkillBadge} ${escapeHtml(event.killer_name || "Jugador no disponible")}
</strong> </strong>
<span class="current-match-killfeed__player-meta">
${renderKillFeedTeamBadge(killerTeam)}
${teamkillBadge}
</span>
</span>
<span <span
class="current-match-killfeed__weapon" class="current-match-killfeed__weapon"
title="${escapeHtml(weapon.label)}" title="${escapeHtml(weapon.label)}"
@@ -300,12 +327,28 @@ function renderKillFeedRow(event) {
<em>${escapeHtml(weapon.label)}</em> <em>${escapeHtml(weapon.label)}</em>
</span> </span>
<span class="current-match-killfeed__player current-match-killfeed__player--victim"> <span class="current-match-killfeed__player current-match-killfeed__player--victim">
${escapeHtml(event.victim_name || "Objetivo no disponible")} <span class="current-match-killfeed__player-name" title="${escapeHtml(event.victim_name || "Objetivo no disponible")}">
${escapeHtml(event.victim_name || "Objetivo no disponible")}
</span>
${renderKillFeedTeamBadge(victimTeam)}
</span> </span>
</article> </article>
`; `;
} }
function getKillFeedTeamDisplay(value) {
const team = getPlayerTeamDisplay(value);
return team.key === "unknown" ? { key: "unknown", label: "N/D" } : team;
}
function renderKillFeedTeamBadge(team) {
return `
<span class="current-match-killfeed__team-badge current-match-killfeed__team-badge--${team.key}">
${escapeHtml(team.label)}
</span>
`;
}
function resolveKillFeedWeapon(value) { function resolveKillFeedWeapon(value) {
const key = normalizeLookupText(value); const key = normalizeLookupText(value);
return CURRENT_MATCH_WEAPONS[key] || { return CURRENT_MATCH_WEAPONS[key] || {
@@ -335,6 +378,7 @@ function renderKillFeedWeaponIcon(weapon) {
function renderPlayerStats(data, nodes, state) { function renderPlayerStats(data, nodes, state) {
const items = Array.isArray(data.items) ? sortPlayerStats(data.items) : []; const items = Array.isArray(data.items) ? sortPlayerStats(data.items) : [];
renderDetectedPlayerCount(items.length, nodes);
if (items.length === 0) { if (items.length === 0) {
state.visibleSignature = ""; state.visibleSignature = "";
nodes.playerStatsShell.innerHTML = ""; nodes.playerStatsShell.innerHTML = "";
@@ -367,6 +411,12 @@ function renderPlayerStats(data, nodes, state) {
setState(nodes.playerStatsState, "Estadisticas parciales derivadas de eventos recientes."); setState(nodes.playerStatsState, "Estadisticas parciales derivadas de eventos recientes.");
} }
function renderDetectedPlayerCount(count, nodes) {
if (nodes.playerCount) {
nodes.playerCount.textContent = `Jugadores detectados: ${count}`;
}
}
function sortPlayerStats(items) { function sortPlayerStats(items) {
return [...items].sort( return [...items].sort(
(left, right) => (left, right) =>