Add REF player grid for cross-report comparison

When an external reference report is loaded and its roster differs from the
current report, a second player section appears below the main grid showing
all REF report players. REF tanks are hidden by default. Clicking any REF
player card toggles their visibility in the REF timeline rows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
xziino 2026-05-21 14:55:25 +02:00
parent e5b70c5589
commit 6363c123b0
3 changed files with 71 additions and 0 deletions

View File

@ -20,6 +20,17 @@
.player-card:hover { border-color: var(--borderem); } .player-card:hover { border-color: var(--borderem); }
.player-card.player-hidden { opacity: 0.35; border-style: dashed; } .player-card.player-hidden { opacity: 0.35; border-style: dashed; }
.player-card--ref { border-color: rgba(100, 160, 255, 0.35); }
.player-card--ref:hover { border-color: rgba(100, 160, 255, 0.7); }
.ref-player-label {
font-size: 11px;
color: var(--t3);
text-transform: uppercase;
letter-spacing: 0.06em;
margin: 10px 0 6px;
}
.player-job-icon { .player-job-icon {
width: 28px; width: 28px;
height: 28px; height: 28px;

View File

@ -85,12 +85,15 @@
let phaseFilter = { startTime: 0, endTime: Infinity }; let phaseFilter = { startTime: 0, endTime: Infinity };
let refEvents = []; let refEvents = [];
let refFightStart = 0; let refFightStart = 0;
let refPlayers = [];
let currentPlayers = [];
let extFights = []; let extFights = [];
let extReportCode = ''; let extReportCode = '';
// ── Player grid ────────────────────────────────────────────────────────── // ── Player grid ──────────────────────────────────────────────────────────
function renderPlayers(players) { function renderPlayers(players) {
currentPlayers = players;
const grid = document.getElementById('player-grid'); const grid = document.getElementById('player-grid');
const order = { healer: 0, dps: 1, tank: 2 }; const order = { healer: 0, dps: 1, tank: 2 };
players.sort((a, b) => { players.sort((a, b) => {
@ -112,6 +115,53 @@
`).join(''); `).join('');
} }
function renderRefPlayers() {
const section = document.getElementById('ref-player-section');
const grid = document.getElementById('ref-player-grid');
if (!refPlayers.length) { section.style.display = 'none'; return; }
const currentNames = new Set(currentPlayers.map(p => p.name));
if (!refPlayers.some(p => !currentNames.has(p.name))) {
section.style.display = 'none';
return;
}
const order = { healer: 0, dps: 1, tank: 2 };
const sorted = [...refPlayers].sort((a, b) => {
const roleCmp = (order[a.role] ?? 2) - (order[b.role] ?? 2);
return roleCmp !== 0 ? roleCmp : a.name.localeCompare(b.name);
});
sorted.filter(p => p.role === 'tank').forEach(p => hiddenPlayerNames.add(p.name));
grid.innerHTML = sorted.map(p => `
<div class="player-card player-card--ref ${hiddenPlayerNames.has(p.name) ? 'player-hidden' : ''}" data-player-name="${p.name}">
<div class="player-job-icon role-${p.role}">${abbr(p.type)}</div>
<div>
<div class="player-name">${p.name}</div>
<div class="player-type">${p.type}</div>
</div>
</div>
`).join('');
section.style.display = '';
}
document.getElementById('ref-player-grid').addEventListener('click', e => {
const card = e.target.closest('.player-card');
if (!card) return;
const name = card.dataset.playerName;
if (hiddenPlayerNames.has(name)) {
hiddenPlayerNames.delete(name);
card.classList.remove('player-hidden');
} else {
hiddenPlayerNames.add(name);
card.classList.add('player-hidden');
}
renderTimeline(lastEvents, lastFightStart);
});
document.getElementById('player-grid').addEventListener('click', e => { document.getElementById('player-grid').addEventListener('click', e => {
const card = e.target.closest('.player-card'); const card = e.target.closest('.player-card');
if (!card) return; if (!card) return;
@ -284,6 +334,8 @@
if (!refId) { if (!refId) {
refEvents = []; refEvents = [];
refFightStart = 0; refFightStart = 0;
refPlayers = [];
renderRefPlayers();
renderTimeline(lastEvents, lastFightStart); renderTimeline(lastEvents, lastFightStart);
return; return;
} }
@ -310,9 +362,11 @@
if (!json.error && !json.reauth) { if (!json.error && !json.reauth) {
refEvents = json.aoe_events ?? []; refEvents = json.aoe_events ?? [];
refFightStart = json.fight_start ?? fight.startTime; refFightStart = json.fight_start ?? fight.startTime;
refPlayers = json.players ?? [];
} }
} catch { } } catch { }
refExtFightSelect.disabled = false; refExtFightSelect.disabled = false;
renderRefPlayers();
renderTimeline(lastEvents, lastFightStart); renderTimeline(lastEvents, lastFightStart);
}); });
@ -584,8 +638,10 @@
lastFightId = null; lastFightId = null;
refEvents = []; refEvents = [];
refFightStart = 0; refFightStart = 0;
refPlayers = [];
extFights = []; extFights = [];
extReportCode = ''; extReportCode = '';
document.getElementById('ref-player-section').style.display = 'none';
refFightSelect.value = ''; refFightSelect.value = '';
refFightSelect.style.display = 'none'; refFightSelect.style.display = 'none';
refExtFightSelect.value = ''; refExtFightSelect.value = '';

View File

@ -18,6 +18,10 @@
</select> </select>
</div> </div>
<div id="player-grid" class="player-grid"></div> <div id="player-grid" class="player-grid"></div>
<div id="ref-player-section" style="display:none">
<div class="ref-player-label">REF Spieler</div>
<div id="ref-player-grid" class="player-grid"></div>
</div>
<div class="ref-ext-row"> <div class="ref-ext-row">
<button id="ref-ext-toggle" class="btn btn-sm">+ Anderer Report</button> <button id="ref-ext-toggle" class="btn btn-sm">+ Anderer Report</button>
<div id="ref-ext-panel" style="display:none"> <div id="ref-ext-panel" style="display:none">