const replayState = { data: null, currentTurn: 1, autoplayTimer: null, frameMode: false, }; const el = { tabs: [...document.querySelectorAll(".top-tab")], panels: [...document.querySelectorAll(".tab-panel")], landingCard: document.getElementById("landing-card"), battleScreen: document.getElementById("battle-screen"), turnTitle: document.getElementById("turn-title"), battleSubtitle: document.getElementById("battle-subtitle"), metaOutcome: document.getElementById("meta-outcome"), metaTotalReward: document.getElementById("meta-total-reward"), playerName: document.getElementById("player-name"), playerStatus: document.getElementById("player-status"), playerTransition: document.getElementById("player-transition"), playerSprite: document.getElementById("player-sprite"), playerHpBar: document.querySelector("#player-hp-bar span"), playerHpLabel: document.getElementById("player-hp-label"), opponentName: document.getElementById("opponent-name"), opponentStatus: document.getElementById("opponent-status"), opponentTransition: document.getElementById("opponent-transition"), opponentSprite: document.getElementById("opponent-sprite"), opponentHpBar: document.querySelector("#opponent-hp-bar span"), opponentHpLabel: document.getElementById("opponent-hp-label"), modelAction: document.getElementById("model-action"), opponentAction: document.getElementById("opponent-action"), rewardLine: document.getElementById("reward-line"), commentaryList: document.getElementById("commentary-list"), playerTeam: document.getElementById("player-team"), opponentTeam: document.getElementById("opponent-team"), validActions: document.getElementById("valid-actions"), summaryLines: document.getElementById("summary-lines"), speed: document.getElementById("speed"), speedValue: document.getElementById("speed-value"), startBtn: document.getElementById("start-btn"), frameBtn: document.getElementById("frame-btn"), prevBtn: document.getElementById("prev-btn"), nextBtn: document.getElementById("next-btn"), jumpBtn: document.getElementById("jump-btn"), turnInput: document.getElementById("turn-input"), }; function toShowdownName(name) { return name.toLowerCase().replace(/[^a-z0-9]/g, ""); } function spriteCandidates(name) { const s = toShowdownName(name); return [ `/static/sprites/${s}.png`, `https://play.pokemonshowdown.com/sprites/gen5/${s}.png`, `https://play.pokemonshowdown.com/sprites/gen4/${s}.png`, `https://play.pokemonshowdown.com/sprites/gen3/${s}.png`, `https://play.pokemonshowdown.com/sprites/gen2/${s}.png`, ]; } function applySprite(img, name, mirrored) { const sources = spriteCandidates(name); img.classList.toggle("player-sprite", mirrored); let index = 0; img.onerror = () => { index += 1; if (index < sources.length) { img.src = sources[index]; } else { img.onerror = null; img.alt = name; img.src = "data:image/svg+xml;utf8," + encodeURIComponent( `` ); } }; img.src = sources[0]; } function hpColor(value) { if (value <= 20) return "#ff6b7a"; if (value <= 50) return "#ffd166"; return "#6de29c"; } function setHp(target, label, hp) { const value = Number.isFinite(hp) ? Math.max(0, Math.min(100, hp)) : 0; target.style.width = `${value}%`; target.style.background = `linear-gradient(90deg, ${hpColor(value)} 0%, ${value <= 20 ? "#ff8793" : value <= 50 ? "#ffe29b" : "#9af0b5"} 100%)`; label.textContent = `${Math.round(value)}%`; } function actionText(label, action) { if (!action) return `${label}: unknown`; const type = action.action || action.type || "?"; return `${label}: ${type} - ${action.choice || "?"}`; } function renderChips(container, items, activeName = null, hidden = 0, formatter = (x) => x) { container.innerHTML = ""; items.forEach((item) => { const value = formatter(item); const chip = document.createElement("span"); chip.className = "chip"; if (typeof item === "string" && item === activeName) chip.classList.add("active"); if (item?.action) chip.classList.add(item.action); chip.textContent = value; container.appendChild(chip); }); for (let i = 0; i < hidden; i += 1) { const chip = document.createElement("span"); chip.className = "chip hidden"; chip.textContent = "Unknown"; container.appendChild(chip); } } function revealedOpponent(turnNumber) { const revealed = []; replayState.data.turns.slice(0, turnNumber).forEach((turn) => { [turn.opponent_active_before.name, turn.opponent_active_after.name].forEach((name) => { if (name && name !== "unknown" && !revealed.includes(name)) revealed.push(name); }); }); return revealed; } function renderSummary() { const { meta, teams } = replayState.data; const playerTeam = teams.player.map((m) => m.name).join(", "); const opponentTeam = teams.opponent.map((m) => m.name).join(", "); const lines = [ ["Outcome", String(meta.outcome || "unknown").toUpperCase()], ["Total Turns", String(meta.total_turns || 0)], ["Total Reward", Number(meta.total_reward || 0).toFixed(2)], ["Player Team", playerTeam], ["Opponent Team", opponentTeam], ["Model", meta.model || "Unknown"], ]; el.summaryLines.innerHTML = lines .map( ([label, value]) => `