Files
2026-09-23 18:59:27 +02:00

549 lines
24 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* OGame Galaxy Viewer v2 — logica frontend */
"use strict";
const $ = (id) => document.getElementById(id);
const state = { galaxy: 1, system: 1, maxGalaxy: 0, maxSystem: 0, boot: null };
/* (utility, avatar e stelle ora in common.js) */
/* ---------------- bootstrap ---------------- */
async function loadBootstrap() {
try {
const b = await api("/api/bootstrap");
state.boot = b;
state.maxGalaxy = b.server.galaxies;
state.maxSystem = b.server.systems;
$("universe-name").textContent = b.server.name;
$("universe-sub").textContent =
`[s${b.server.number}-${b.server.community}] · velocità ${b.server.speed}x · v${b.server.version}`;
$("max-galaxy").textContent = b.server.galaxies;
$("max-system").textContent = b.server.systems;
$("meta-data-age").textContent = b.fetched.players
? `Dati: ${b.fetched.players} UTC` : "";
$("meta-reports").innerHTML =
`Spie archiviate: <b>${b.reportStats.count}</b> su ${b.reportStats.planets} pianeti`;
} catch (e) {
setMsg("err", "Impossibile contattare il server: " + e.message);
}
}
/* ---------------- galassia ---------------- */
async function loadGalaxy() {
$("nav-status").textContent = `Caricamento G${state.galaxy}:S${state.system}…`;
try {
const data = await api(`/api/galaxy?g=${state.galaxy}&s=${state.system}`);
renderGalaxy(data);
$("nav-status").textContent = "";
$("inp-galaxy").value = state.galaxy;
$("inp-system").value = state.system;
} catch (e) {
setMsg("err", e.message);
$("nav-status").textContent = "";
}
}
const stClass = (s) => "st-" + (s || "");
function renderGalaxy(data) {
const body = $("galaxy-body");
body.innerHTML = "";
let any = false;
for (const cell of data.cells) {
const tr = document.createElement("tr");
let td = document.createElement("td");
td.className = "c-pos";
td.textContent = cell.pos;
tr.appendChild(td);
td = document.createElement("td");
if (cell.planet) {
any = true;
const mine = cell.planet.mine ? " planet-mine" : "";
const ro = cell.planet.report_only;
const roTag = ro
? ` <span class="ro-tag" title="Noto solo da un rapporto di spionaggio">da rapporto</span>` : "";
const ptitle = ro ? "Pianeta noto solo da un rapporto di spionaggio" : "Apri dettagli e rapporti";
td.innerHTML =
`<div class="pcell${mine}" title="${ptitle}">` +
`<span class="p-avatar">${artSVG(cell.planet.coords, 26)}</span>` +
`<span><span class="pname">${esc(cell.planet.name)}</span><br>` +
`<span class="pcoords">${esc(cell.planet.coords)}</span>${roTag}</span></div>`;
td.querySelector(".pcell").onclick = () => openPlanet(cell.pos);
}
tr.appendChild(td);
td = document.createElement("td");
if (cell.moon) {
td.innerHTML = `<span class="mcell">${artSVG(cell.planet.coords, 22, true)}` +
`<span>${Number(cell.moon.size).toLocaleString("it")}</span></span>`;
td.querySelector(".mcell").onclick = () => openPlanet(cell.pos, "moon");
}
tr.appendChild(td);
td = document.createElement("td");
if (cell.planet && cell.player) {
const p = cell.player;
const g = p.rank_total ?? "–";
const m = p.rank_military ?? "–";
td.innerHTML =
`<span class="pname-status player-link ${stClass(p.status)}" ` +
`title="Tutti i pianeti e i rapporti di questo giocatore">${esc(p.name)}</span>` +
`<span class="pranks" title="Posizioni in classifica — Generale: #${g} · Armamenti: #${m}">` +
`<span class="rk">${icon("trophy", 11)} ${g}</span><span class="rk">${icon("swords", 11)} ${m}</span></span>`;
const pn = td.querySelector(".player-link");
if (pn) pn.onclick = () => openPlayer(p.id);
}
tr.appendChild(td);
td = document.createElement("td");
if (cell.player && cell.player.alliance_tag) {
td.innerHTML = `<span class="ally-tag">[${esc(cell.player.alliance_tag)}]</span>`;
}
tr.appendChild(td);
td = document.createElement("td");
td.className = "c-rep";
if (cell.reports && cell.reports.length) {
const n = cell.reports.length;
const hasMoon = cell.reports.some((r) => r.moon);
td.innerHTML = `<span class="rep-badge${hasMoon ? " rep-moon" : ""}" ` +
`title="${n} rapporto/i di spionaggio">${icon("badge_spy", 12)} ${n}</span>`;
td.querySelector(".rep-badge").onclick = () => openPlanet(cell.pos);
}
tr.appendChild(td);
body.appendChild(tr);
}
$("empty-state").classList.toggle("hidden", any);
}
/* ---------------- popup: pianeta ---------------- */
function popupHead(avatar, title, sub) {
$("popup-avatar").innerHTML = avatar;
$("popup-title").textContent = title;
$("popup-sub").textContent = sub;
}
async function openPlanet(pos, focusMoon) {
return openPlanetAt(`${state.galaxy}:${state.system}:${pos}`, focusMoon);
}
// Apre il pianeta date le coordinate `g:s:p` (può essere in un altro sistema,
// senza spostare la vista della galassia).
async function openPlanetAt(coords, focusMoon) {
const [g, s, p] = String(coords).split(":").map(Number);
_lastPlanetCoords = `${g}:${s}:${p}`;
try {
const data = await api(`/api/planet?g=${g}&s=${s}&p=${p}`);
renderPlanetPopup(data, focusMoon);
$("overlay").classList.remove("hidden");
} catch (e) {
setMsg("err", e.message);
}
}
// Apre la finestra giocatore: tutti i pianeti + tutti i rapporti.
async function openPlayer(pid) {
try {
const d = await api(`/api/player?id=${encodeURIComponent(pid)}`);
renderPlayerPopup(d);
$("overlay").classList.remove("hidden");
} catch (e) {
setMsg("err", e.message);
}
}
const STATUS_LABEL = { "": "attivo", i: "inattivo", v: "in vacanza", vi: "vacanza + inattivo", a: "staff" };
const CAT_ICON = {
buildings: "cat_buildings", lfbuildings: "cat_lf",
research: "cat_research", ships: "cat_ships", defense: "cat_defense",
};
const ITEM_ICON = {
buildings: { 1: "b_1", 2: "b_2", 3: "b_3", 4: "b_4", 12: "b_12", 14: "b_14", 15: "b_15",
21: "b_21", 22: "b_22", 23: "b_23", 24: "b_24", 31: "b_31", 33: "b_33",
34: "b_34", 36: "b_36", 41: "b_41", 42: "b_42", 43: "b_43", 44: "b_44" },
research: { 106: "r_106", 108: "r_108", 109: "r_109", 110: "r_110", 111: "r_111",
113: "r_113", 114: "r_114", 115: "r_115", 117: "r_117", 118: "r_118",
120: "r_120", 121: "r_121", 122: "r_122", 123: "r_123", 124: "r_124",
199: "r_199" },
ships: { 202: "ship_202", 203: "ship_203", 204: "ship_204", 205: "ship_205",
206: "ship_206", 207: "ship_207", 208: "ship_208", 209: "ship_209",
210: "ship_210", 211: "ship_211", 212: "ship_212", 213: "ship_213",
214: "ship_214", 215: "ship_215", 217: "ship_217", 218: "ship_218", 219: "ship_219" },
defense: { 401: "d_401", 402: "d_402", 403: "d_403", 404: "d_404", 405: "d_405",
406: "d_406", 407: "d_407", 408: "d_408", 502: "d_502", 503: "d_503" },
lfbuildings: { 11101: "lf_11101", 11102: "lf_11102" },
};
function itemIconName(cat, id) {
const t = ITEM_ICON[cat] || {};
return t[id] || CAT_ICON[cat] || "cat_ships";
}
function renderPlanetPopup(data, focusMoon) {
const pl = data.planet, player = data.player;
_lastPlanet = data;
_lastPlayer = null;
const mine = player.id === String(state.boot.myPlayer.id) ? " (tu)" : "";
const coords = pl.coords;
const hasMoon = !!(pl.moon);
popupHead(artSVG(coords, 52), `${pl.name} [${coords}]`, "Pianeta · clicca un rapporto per i dettagli");
let h = "";
h += `<div class="kv">`;
h += `<span class="chip"><span class="k">Giocatore</span> ` +
`<span class="v ${stClass(player.status)}">${esc(player.name)}</span>${mine}</span>`;
h += `<span class="chip"><span class="k">Stato</span> <span class="v">${STATUS_LABEL[player.status] || "—"}</span></span>`;
if (player.rank_total || player.rank_military) {
h += `<span class="chip"><span class="k">Classifica</span> <span class="v">` +
`${icon("trophy", 13)} #${player.rank_total ?? "—"} · ${icon("swords", 13)} #${player.rank_military ?? "—"}</span></span>`;
}
if (player.alliance_tag) {
h += `<span class="chip"><span class="k">Alleanza</span> ` +
`<span class="v">[${esc(player.alliance_tag)}]</span> ${esc(player.alliance_name || "")}</span>`;
}
if (hasMoon) {
h += `<span class="chip"><span class="k">Luna</span> <span class="v">` +
`${esc(pl.moon.name)} · ${Number(pl.moon.size).toLocaleString("it")} km</span></span>`;
}
h += `<span class="chip"><span class="k">Pianeti</span> <span class="v">${1 + (data.other_planets || []).length}</span></span>`;
if (pl.report_only) {
h += `<span class="chip"><span class="k">Origine</span> <span class="v">solo da rapporto</span></span>`;
}
h += `</div>`;
if (data.other_planets && data.other_planets.length) {
h += `<div class="sec"><h4>Altri pianeti di ${esc(player.name)}</h4><div class="kv">`;
h += data.other_planets.map((o) =>
`<span class="chip chip-other" data-go="${esc(o.coords)}">${artSVG(o.coords, 17)}` +
`${esc(o.name)} [${esc(o.coords)}]${o.moon ? " " + artSVG(o.coords, 13, true) : ""}</span>`).join("");
h += `</div></div>`;
}
const rpt = (list, moon) =>
`<div class="sec"><h4>${moon ? "Spionaggi sulla luna" : "Spionaggi sul pianeta"}` +
` <span class="count-pill">${list.length}</span></h4>` +
(list.length
? `<div class="rpt-grid">${list.map(rptCard).join("")}</div>`
: `<div class="empty-note">${moon
? "La luna non è mai stata spiata."
: "Nessun rapporto salvato. Spia in gioco e incolla l'API string qui sopra."}</div>`) +
`</div>`;
// sezione pianeta: sempre visibile (lista o invito a importare)
h += rpt(data.reports, false);
// sezione luna: solo se esiste una luna o ci sono spionaggi sulla luna
if (hasMoon || data.moon_reports.length) h += rpt(data.moon_reports, true);
$("popup-body").innerHTML = h;
$("popup-body").querySelectorAll(".chip-other").forEach((c) => {
c.onclick = () => { closePopup(); goToCoords(c.dataset.go); };
});
$("popup-body").querySelectorAll(".rpt-card").forEach((c) => {
c.onclick = () => openReport(c.dataset.sr);
});
if (focusMoon === "moon") {
const secs = $("popup-body").querySelectorAll(".sec h4");
for (const s of secs) if (s.textContent.includes("luna")) { s.scrollIntoView({ block: "start" }); break; }
}
}
/* ---------------- popup: giocatore ---------------- */
function renderPlayerPopup(d) {
const player = d.player, planets = d.planets || [], reports = d.reports || [];
_lastPlayer = d;
_lastPlanet = null;
const mine = String(player.id) === String(state.boot.myPlayer.id) ? " (tu)" : "";
const avatar = d.avatar || (planets[0] && planets[0].coords) || (player.name + player.id);
popupHead(artSVG(avatar, 52), player.name + mine,
`${planets.length} pianeti · ${reports.length} rapporti di spionaggio`);
let h = "";
h += `<div class="kv">`;
h += `<span class="chip"><span class="k">Stato</span> <span class="v ${stClass(player.status)}">` +
`${STATUS_LABEL[player.status] || "—"}</span></span>`;
if (player.rank_total || player.rank_military) {
h += `<span class="chip"><span class="k">Classifica</span> <span class="v">` +
`${icon("trophy", 13)} #${player.rank_total ?? "—"} · ${icon("swords", 13)} #${player.rank_military ?? "—"}</span></span>`;
}
if (player.alliance_tag) {
h += `<span class="chip"><span class="k">Alleanza</span> ` +
`<span class="v">[${esc(player.alliance_tag)}]</span> ${esc(player.alliance_name || "")}</span>`;
}
h += `<span class="chip"><span class="k">Spie</span> <span class="v">${reports.length}</span></span>`;
h += `</div>`;
// pianeti del giocatore
h += `<div class="sec"><h4>Pianeti <span class="count-pill">${planets.length}</span></h4>`;
h += `<div class="kv">` + planets.map((o) =>
`<span class="chip chip-other" data-coords="${esc(o.coords)}">${artSVG(o.coords, 17)}` +
`${esc(o.name)} [${esc(o.coords)}]${o.moon ? " " + artSVG(o.coords, 13, true) : ""}</span>`
).join("") + `</div></div>`;
// tutti i rapporti del giocatore (pianeti e lune)
h += `<div class="sec"><h4>Rapporti di spionaggio <span class="count-pill">${reports.length}</span></h4>` +
(reports.length
? `<div class="rpt-grid">${reports.map(rptCard).join("")}</div>`
: `<div class="empty-note">Nessun rapporto salvato su questo giocatore.</div>`) +
`</div>`;
$("popup-body").innerHTML = h;
$("popup-body").querySelectorAll(".chip-other").forEach((c) => {
c.onclick = () => openPlanetAt(c.dataset.coords);
});
$("popup-body").querySelectorAll(".rpt-card").forEach((c) => {
c.onclick = () => openReport(c.dataset.sr);
});
}
/* ---------------- card rapporto ---------------- */
function actClass(a) {
if (a === null || a === undefined) return "act-none";
if (a <= 0) return "act-0";
if (a <= 15) return "act-15";
if (a <= 30) return "act-30";
return "act-60";
}
function actLabel(a) {
if (a === null || a === undefined) return "attività sconosciuta";
if (a <= 0) return "inattivo ≥7gg";
return `attivo ≤ ${a} min`;
}
function fmtEv(evt) {
if (!evt) return "";
const d = new Date(evt);
return isNaN(d) ? evt : d.toLocaleString("it-IT", { dateStyle: "medium", timeStyle: "short" });
}
function rptCard(rec) {
const c = rec.categories || {};
const loot = rec.loot_percentage !== null && rec.loot_percentage !== undefined;
const parts = [];
if (c.ships) parts.push(`${icon("cat_ships", 13)} ${c.ships}`);
if (c.defense) parts.push(`${icon("cat_defense", 13)} ${c.defense}`);
if (c.buildings) parts.push(`${icon("cat_buildings", 13)} ${c.buildings}`);
if (c.research) parts.push(`${icon("cat_research", 13)} ${c.research}`);
return `<div class="rpt-card" data-sr="${esc(rec.sr_id)}">` +
`<div class="top"><span class="act-dot ${actClass(rec.activity)}"></span>` +
`<span class="rpt-date">${fmtEv(rec.event_time)}</span>` +
`<span class="rpt-kind ${rec.moon ? "moon" : "planet"}">${rec.moon ? "luna" : "pianeta"}</span></div>` +
`<div class="rpt-body"><div class="rpt-target">${esc(rec.defender_planet_name || "?")}` +
(rec.coords ? ` <span class="rpt-coords">[${esc(rec.coords)}]</span>` : "") +
`</div><div class="rpt-from">da ${esc(rec.attacker_name || "?")}` +
(loot ? ` · bottino ${rec.loot_percentage}%` : "") + `</div></div>` +
`<div class="rpt-foot"><span class="meta-dots">${parts.map((p) => `<span class="m">${p}</span>`).join("") || "—"}</span>` +
`<span class="rpt-open">Apri ›</span></div></div>`;
}
/* ---------------- dettaglio rapporto ---------------- */
let _lastPlanet = null;
let _lastPlanetCoords = null;
let _lastPlayer = null;
async function openReport(srId) {
try {
const d = await api(`/api/reports/${srId}`);
renderReportDetail(d.meta, d.data);
} catch (e) {
setMsg("err", e.message);
}
}
async function deleteReport(srId) {
if (!confirm("Eliminare definitivamente questo rapporto di spionaggio?")) return;
try {
await api(`/api/reports/${srId}`, { method: "DELETE" });
if (_lastPlanetCoords) {
await openPlanetAt(_lastPlanetCoords); // ricarica il pianeta: lista aggiornata
} else if (_lastPlayer && _lastPlayer.player) {
await openPlayer(_lastPlayer.player.id); // ricarica il giocatore: lista aggiornata
} else {
closePopup();
}
loadGalaxy(); // aggiorna i badge "spie" nella galassia
} catch (e) {
setMsg("err", "Eliminazione non riuscita: " + e.message);
}
}
function renderReportDetail(meta, rep) {
const gen = rep.generic || {}, det = rep.details || {}, res = det.resources || {};
const coords = meta.coords || gen.defender_planet_coordinates || "";
popupHead(artSVG(coords, 52), `Spionaggio — ${meta.defender_planet_name || "?"}`,
`${coords}${meta.planet_type === 3 ? " (luna)" : ""} · ${fmtEv(meta.event_time)}`);
let h = "";
h += `<div class="rpt-actions">` +
`<button class="back-btn" id="btn-back">${icon("ui_chev_l", 14)} Indietro al pianeta</button>` +
`<button class="del-btn" id="btn-del">${icon("ui_trash", 14)} Elimina rapporto</button>` +
`</div>`;
h += `<div class="kv">`;
h += `<span class="chip"><span class="k">Difensore</span> <span class="v">${esc(meta.defender_name || "?")}</span></span>`;
h += `<span class="chip"><span class="k">Attaccante</span> <span class="v">${esc(gen.attacker_name || "?")}</span></span>`;
h += `<span class="chip"><span class="k">Attività</span> <span class="v">${actLabel(meta.activity)}</span></span>`;
if (meta.loot_percentage !== null && meta.loot_percentage !== undefined)
h += `<span class="chip"><span class="k">Bottino</span> <span class="v">${meta.loot_percentage}%</span></span>`;
if (gen.spy_fail_chance !== undefined)
h += `<span class="chip"><span class="k">Rischio fall.</span> <span class="v">${gen.spy_fail_chance}%</span></span>`;
if (gen.debris_metal || gen.debris_crystal)
h += `<span class="chip"><span class="k">Campo detriti</span> <span class="v">${icon("extra_debris", 13)} ${num(gen.debris_metal)} / ${num(gen.debris_crystal)}</span></span>`;
h += `</div>`;
const rt = (lab, ico, val, cls) =>
`<div class="res-tile ${cls}"><span class="r-ico">${ico}</span><span>` +
`<div class="r-lab">${lab}</div><div class="r-val">${val}</div></span></div>`;
h += `<div class="res-grid">`;
h += rt("Metallo", icon("res_metal", 20), num(res.metal), "res-metal");
h += rt("Cristallo", icon("res_crystal", 20), num(res.crystal), "res-crystal");
h += rt("Deuterio", icon("res_deuterium", 20), num(res.deuterium), "res-deuterium");
if (res.population) h += rt("Popolazione", icon("res_pop", 20), num(res.population), "res-pop");
if (res.food) h += rt("Cibo", icon("res_food", 20), num(res.food), "res-food");
h += `</div>`;
const failed = Object.entries({
"edifici": gen.failed_buildings, "ricerca": gen.failed_research,
"navi": gen.failed_ships, "difese": gen.failed_defense,
}).filter(([, v]) => v).map(([k]) => k);
if (failed.length)
h += `<div class="warn">${icon("ui_warn", 14)} Spionaggio fallito su: ${failed.join(", ")} — dati incompleti.</div>`;
h += techSection("Edifici", "buildings", det.buildings, (b) => [b.building_type, `L. ${b.level}`], "liv.");
h += techSection("Edifici Lifeform", "lfbuildings", det.lfbuildings, (b) => [b.lfbuilding_type, `L. ${b.level}`], "liv.");
h += techSection("Ricerca", "research", det.research, (b) => [b.research_type, `L. ${b.level}`], "liv.");
h += techSection("Navi", "ships", det.ships, (b) => [b.ship_type, num(b.count)], "unità");
h += techSection("Difese", "defense", det.defense, (b) => [b.defense_type, num(b.count)], "unità");
$("popup-body").innerHTML = h;
$("btn-back").onclick = () => {
if (_lastPlanet) renderPlanetPopup(_lastPlanet);
else if (_lastPlayer) renderPlayerPopup(_lastPlayer);
else closePopup();
};
$("btn-del").onclick = () => deleteReport(meta.sr_id);
}
function techSection(title, cat, rows, get, valLabel) {
if (!rows || !rows.length) return "";
const cards = rows.map((it) => {
const [tid, val] = get(it);
return `<div class="tcard cat-${cat}" title="${esc(itemName(cat, tid))}">` +
`<span class="tico">${icon(itemIconName(cat, tid), 19)}</span>` +
`<span class="tname">${esc(itemName(cat, tid))}</span>` +
`<span class="tval">${val}</span></div>`;
}).join("");
return `<div class="sec"><h4>${title} <span class="count-pill">${rows.length}</span></h4>` +
`<div class="tech-grid">${cards}</div></div>`;
}
/* ---------------- nomi e icone elementi ---------------- */
const NAMES = {
buildings: {
1: "Miniera di metallo", 2: "Miniera di cristallo", 3: "Sintetizzatore di deuterio",
4: "Centrale solare", 12: "Centrale a fusione", 14: "Fabbrica di robot",
15: "Fabbrica di naniti", 21: "Cantiere spaziale", 22: "Deposito di metallo",
23: "Deposito di cristallo", 24: "Serbatoio di deuterio", 31: "Laboratorio di ricerca",
33: "Terraformer", 34: "Deposito dell'alleanza", 36: "Dock di riparazione",
41: "Base lunare", 42: "Falanx sensoriale", 43: "Portale quantico", 44: "Silo missilistico",
},
research: {
106: "Tecnologia spionaggio", 108: "Tecnologia informatica", 109: "Tecnologia delle armi",
110: "Tecnologia scudi", 111: "Tecnologia corazzatura", 113: "Tecnologia energetica",
114: "Tecnologia iperspazio", 115: "Motore a combustione", 117: "Motore a impulso",
118: "Motore iperspaziale", 120: "Tecnologia laser", 121: "Tecnologia ionica",
122: "Tecnologia al plasma", 123: "Rete di ricerca intergalattica",
124: "Astrofisica", 199: "Tecnologia gravitoni",
},
ships: {
202: "Cargo leggero", 203: "Cargo pesante", 204: "Caccia leggero", 205: "Caccia pesante",
206: "Incrociatore", 207: "Nave da battaglia", 208: "Nave colonizzatrice",
209: "Riciclatore", 210: "Sonda di spionaggio", 211: "Bombardiere", 212: "Satellite solare",
213: "Corazzata", 214: "Morte Nera", 215: "Incrociatore da battaglia", 217: "Crawler",
218: "Reaper", 219: "Pathfinder",
},
defense: {
401: "Lanciamissili", 402: "Laser leggero", 403: "Laser pesante", 404: "Cannone Gauss",
405: "Cannone ionico", 406: "Cannone al plasma", 407: "Cupola piccola", 408: "Cupola grande",
502: "Missili anti-balistici", 503: "Missili interplanetari",
},
lfbuildings: { 11101: "Campo di coltivazione", 11102: "Centro di ricerca" },
};
function itemName(cat, id) {
const t = NAMES[cat] || {};
return t[id] || (cat === "lfbuildings" ? "Edificio Lifeform #" + id : cat + " #" + id);
}
/* ---------------- navigazione ---------------- */
function setSystem(s) { state.system = Math.max(1, Math.min(state.maxSystem, s)); loadGalaxy(); }
function setGalaxy(g) { state.galaxy = Math.max(1, Math.min(state.maxGalaxy, g)); loadGalaxy(); }
function closePopup() { $("overlay").classList.add("hidden"); _lastPlanet = null; _lastPlayer = null; }
function goToCoords(c) {
const [g, s] = c.split(":").map(Number);
closePopup();
state.galaxy = Math.max(1, Math.min(state.maxGalaxy, g || 1));
state.system = Math.max(1, Math.min(state.maxSystem, s || 1));
loadGalaxy();
}
/* ---------------- messaggi / import ---------------- */
function setMsg(kind, text, ms = 6000) {
const el = $("import-msg");
el.className = kind;
el.textContent = text;
if (ms) setTimeout(() => { el.textContent = ""; }, ms);
}
async function importReport() {
const token = $("inp-token").value.trim();
if (!token) { setMsg("err", "Incolla prima l'API string."); return; }
const btn = $("btn-import");
btn.disabled = true;
setMsg("ok", "Recupero del rapporto…");
try {
const res = await api("/api/reports", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token }),
});
$("inp-token").value = "";
setMsg("ok",
res.created
? `Importato: ${res.report.defender_name} — ${res.coords}${res.planet_type === 3 ? " (luna)" : ""}`
: `Rapporto già in archivio (${res.coords}).`);
loadGalaxy();
} catch (e) {
setMsg("err", e.message);
} finally {
btn.disabled = false;
}
}
/* ---------------- eventi ---------------- */
$("btn-import").onclick = importReport;
$("inp-token").addEventListener("keydown", (e) => { if (e.key === "Enter") importReport(); });
$("btn-prev-sys").onclick = () => setSystem(state.system - 1);
$("btn-next-sys").onclick = () => setSystem(state.system + 1);
$("btn-refresh").onclick = () => loadGalaxy();
$("inp-system").addEventListener("change", (e) => setSystem(parseInt(e.target.value, 10) || 1));
$("inp-galaxy").addEventListener("change", (e) => setGalaxy(parseInt(e.target.value, 10) || 1));
$("popup-close").onclick = closePopup;
$("overlay").addEventListener("click", (e) => { if (e.target === $("overlay")) closePopup(); });
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && !$("overlay").classList.contains("hidden")) closePopup();
});
/* ---------------- init ---------------- */
initStars();
const il = document.getElementById("import-label");
if (il) il.innerHTML = icon("ui_import", 15) + ' <span>Nuovo rapporto</span>';
const tli = document.querySelector(".tl-ico");
if (tli) tli.innerHTML = icon("trophy", 14);
const pcl = document.getElementById("popup-close");
if (pcl) pcl.innerHTML = icon("ui_close", 16);
const _qs = new URLSearchParams(location.search);
const _g = parseInt(_qs.get("g") || "", 10);
const _s = parseInt(_qs.get("s") || "", 10);
(async () => {
await loadBootstrap();
if (_g) state.galaxy = Math.max(1, Math.min(state.maxGalaxy, _g));
if (_s) state.system = Math.max(1, Math.min(state.maxSystem, _s));
await loadGalaxy();
})();