primo commit
This commit is contained in:
+548
@@ -0,0 +1,548 @@
|
||||
/* 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();
|
||||
})();
|
||||
@@ -0,0 +1,107 @@
|
||||
/* Utility condivise tra le pagine (galassia e classifiche) */
|
||||
"use strict";
|
||||
|
||||
/* ---------------- utility base ---------------- */
|
||||
function esc(s) {
|
||||
return String(s ?? "").replace(/[&<>"']/g, (c) =>
|
||||
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
||||
}
|
||||
function num(v) { return (v === null || v === undefined) ? "—" : Number(v).toLocaleString("it"); }
|
||||
function hashStr(s) { let h = 0; for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0; return Math.abs(h); }
|
||||
|
||||
async function api(path, opts) {
|
||||
const r = await fetch(path, opts);
|
||||
let data = null;
|
||||
try { data = await r.json(); } catch (_) { /* noop */ }
|
||||
if (!r.ok) throw new Error((data && data.error) || ("Errore HTTP " + r.status));
|
||||
return data;
|
||||
}
|
||||
|
||||
/* ---------------- icone SVG (window.ICONS da icons.js) ---------------- */
|
||||
function icon(name, size = 16) {
|
||||
const it = (window.ICONS || {})[name];
|
||||
if (!it) return "";
|
||||
return `<svg class="ic" width="${size}" height="${size}" viewBox="${it.vb}" ` +
|
||||
`xmlns="http://www.w3.org/2000/svg" aria-hidden="true">${it.body}</svg>`;
|
||||
}
|
||||
|
||||
/* ---------------- avatar pianeti/lune (SVG) ---------------- */
|
||||
let _svgSeq = 0;
|
||||
const PLANET_PAL = [
|
||||
["#ffd9a0", "#e0762c"], ["#f2b3a0", "#b3422f"], ["#ffb1c0", "#a0275a"],
|
||||
["#9fd8ff", "#2a6fc4"], ["#8fe8c0", "#1e7a5f"], ["#d3b8ff", "#6d3fc4"],
|
||||
["#f5e3a0", "#c2a22e"], ["#c3d0e2", "#5c6f8c"],
|
||||
];
|
||||
const LUNA_PAL = [["#e6e6e6", "#9a9a9a"]];
|
||||
|
||||
function artSVG(seedKey, size, moon = false) {
|
||||
const seed = hashStr(seedKey) || 1;
|
||||
const r = 15; // viewBox 32
|
||||
const pal = (moon ? LUNA_PAL : PLANET_PAL)[seed % (moon ? 1 : PLANET_PAL.length)];
|
||||
const id = "g" + (++_svgSeq);
|
||||
let extras = "";
|
||||
if (moon) {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const cx = 7 + ((seed >> (i * 3)) % 18);
|
||||
const cy = 7 + ((seed >> (i * 5 + 2)) % 18);
|
||||
const cr = 2 + ((seed >> (i * 2 + 1)) % 3);
|
||||
extras += `<circle cx="${cx}" cy="${cy}" r="${cr}" fill="rgba(0,0,0,.18)"/>` +
|
||||
`<circle cx="${cx}" cy="${cy}" r="${cr}" fill="none" stroke="rgba(255,255,255,.28)" stroke-width=".7"/>`;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const cx = 8 + ((seed >> (i * 4)) % 14);
|
||||
const cy = 6 + ((seed >> (i * 3 + 1)) % 14);
|
||||
extras += `<circle cx="${cx}" cy="${cy}" r="${5 + ((seed >> i) % 5)}" fill="rgba(255,255,255,.10)"/>`;
|
||||
}
|
||||
}
|
||||
return `<svg width="${size}" height="${size}" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">` +
|
||||
`<defs><radialGradient id="${id}" cx=".35" cy=".3" r=".95">` +
|
||||
`<stop offset="0" stop-color="${pal[0]}"/><stop offset="1" stop-color="${pal[1]}"/>` +
|
||||
`</radialGradient></defs>` +
|
||||
`<circle cx="16" cy="16" r="${r}" fill="url(#${id})"/>` +
|
||||
extras +
|
||||
`<path d="M2 17a14 14 0 0 0 28 0 12 9 0 0 1 -28 0z" fill="rgba(0,0,0,.30)"/>` +
|
||||
`<circle cx="16" cy="16" r="${r}" fill="none" stroke="rgba(0,0,0,.35)" stroke-width="1"/>` +
|
||||
`</svg>`;
|
||||
}
|
||||
|
||||
/* ---------------- sfondo stellare ---------------- */
|
||||
function initStars() {
|
||||
const cv = document.getElementById("space");
|
||||
if (!cv) return;
|
||||
let ctx = null;
|
||||
try { ctx = cv.getContext("2d"); } catch (_) { /* noop */ }
|
||||
if (!ctx) return; // mai bloccare il resto dell'app
|
||||
let stars = [], tw = [];
|
||||
function resize() {
|
||||
cv.width = innerWidth; cv.height = innerHeight;
|
||||
stars = []; tw = [];
|
||||
const n = Math.floor((cv.width * cv.height) / 6500);
|
||||
for (let i = 0; i < n; i++) {
|
||||
stars.push({ x: Math.random() * cv.width, y: Math.random() * cv.height,
|
||||
r: Math.random() * 1.1 + .25, a: Math.random() * .5 + .2 });
|
||||
}
|
||||
for (let i = 0; i < 26; i++) {
|
||||
tw.push({ x: Math.random() * cv.width, y: Math.random() * cv.height,
|
||||
r: Math.random() * 1.2 + .4, p: Math.random() * Math.PI * 2, s: .8 + Math.random() });
|
||||
}
|
||||
}
|
||||
resize();
|
||||
addEventListener("resize", resize);
|
||||
(function draw(t) {
|
||||
ctx.clearRect(0, 0, cv.width, cv.height);
|
||||
for (const s of stars) {
|
||||
ctx.globalAlpha = s.a; ctx.fillStyle = "#cfe0ff";
|
||||
ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, 7); ctx.fill();
|
||||
}
|
||||
const k = t / 1000;
|
||||
for (const s of tw) {
|
||||
ctx.globalAlpha = .25 + .6 * Math.abs(Math.sin(k * s.s + s.p));
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, 7); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
requestAnimationFrame(draw);
|
||||
})(performance.now());
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Galaxy Viewer — OGame</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=5">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="space" aria-hidden="true"></canvas>
|
||||
|
||||
<div id="app">
|
||||
<header id="topbar">
|
||||
<div class="brand">
|
||||
<span class="logo"><svg width="20" height="20" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="5" fill="none" stroke="currentColor" stroke-width="2"/><ellipse cx="12" cy="12" rx="10" ry="3.4" fill="none" stroke="currentColor" stroke-width="1.4" transform="rotate(-22 12 12)"/></svg></span>
|
||||
<span>
|
||||
<span id="universe-name">Galaxy Viewer</span>
|
||||
<span id="universe-sub" class="dim"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="tools">
|
||||
<div class="nav-group">
|
||||
<button id="btn-prev-sys" title="Sistema precedente"><svg width="15" height="15" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 5.5L8 12l6.5 6.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
|
||||
<label>G <input id="inp-galaxy" type="number" min="1" value="1"></label>
|
||||
<span class="dim">/ <span id="max-galaxy">?</span></span>
|
||||
<label>S <input id="inp-system" type="number" min="1" value="1"></label>
|
||||
<span class="dim">/ <span id="max-system">?</span></span>
|
||||
<button id="btn-next-sys" title="Sistema successivo"><svg width="15" height="15" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M9.5 5.5L16 12l-6.5 6.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
|
||||
</div>
|
||||
<button id="btn-refresh" title="Ricarica la vista"><svg width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M20 12a8 8 0 1 1-2.34-5.66" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/><path d="M20 3.5V8h-4.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
|
||||
<a class="toplink" href="/rankings" title="Classifiche e ricerca giocatori"><span class="tl-ico"></span> Classifiche</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section id="meta-bar">
|
||||
<span id="meta-data-age" class="dim"></span>
|
||||
<span id="meta-reports" class="dim"></span>
|
||||
<span id="nav-status"></span>
|
||||
</section>
|
||||
|
||||
<section id="import-box">
|
||||
<span class="lbl" id="import-label">Nuovo rapporto</span>
|
||||
<input id="inp-token" type="text" spellcheck="false"
|
||||
placeholder="API string, es. sr-ar-170-98018ad7...">
|
||||
<button id="btn-import">Importa</button>
|
||||
<span id="import-msg"></span>
|
||||
</section>
|
||||
|
||||
<main id="galaxy-wrap">
|
||||
<table id="galaxy">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="c-pos">Pos</th>
|
||||
<th>Pianeta</th>
|
||||
<th>Luna</th>
|
||||
<th>Giocatore</th>
|
||||
<th>Alleanza</th>
|
||||
<th class="c-rep">Spie</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="galaxy-body"></tbody>
|
||||
</table>
|
||||
<div id="empty-state" class="empty-note hidden">Nessun pianeta in questo sistema.</div>
|
||||
</main>
|
||||
|
||||
<footer class="dim">API pubblica OGame · aggiornamento giornaliero · rapporti archiviati localmente · icone: <a href="https://game-icons.net" target="_blank" rel="noopener">game-icons.net</a> (CC BY 3.0)</footer>
|
||||
</div>
|
||||
|
||||
<!-- Popup pianeta / rapporto -->
|
||||
<div id="overlay" class="hidden">
|
||||
<div id="popup">
|
||||
<header id="popup-head">
|
||||
<div id="popup-avatar" class="avatar"></div>
|
||||
<div>
|
||||
<div id="popup-title"></div>
|
||||
<div id="popup-sub"></div>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<button class="icon-btn" id="popup-close" title="Chiudi"><svg width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg></button>
|
||||
</header>
|
||||
<div id="popup-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/icons.js?v=1"></script>
|
||||
<script src="/static/common.js?v=5"></script>
|
||||
<script src="/static/app.js?v=5"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Classifiche — Galaxy Viewer</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=5">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="space" aria-hidden="true"></canvas>
|
||||
|
||||
<div id="app">
|
||||
<header id="topbar">
|
||||
<div class="brand">
|
||||
<span class="logo" id="rk-logo"></span>
|
||||
<span>
|
||||
<span id="rk-universe">Classifiche</span>
|
||||
<span id="rk-sub" class="dim"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="tools">
|
||||
<a class="toplink" href="/">◀ Galassia</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="rk-head">
|
||||
<div class="rk-tabs" id="rk-tabs">
|
||||
<button class="rk-tab on" data-type="total">Generale</button>
|
||||
<button class="rk-tab" data-type="military">Armamenti</button>
|
||||
<button class="rk-tab" data-type="fleet">Top Flotte</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section id="rk-filter">
|
||||
<span class="lbl">Stato</span>
|
||||
<select id="f-status">
|
||||
<option value="all">Tutti</option>
|
||||
<option value="active">Attivi</option>
|
||||
<option value="inactive">Inattivi</option>
|
||||
</select>
|
||||
<span class="lbl rg">Posizione</span>
|
||||
<div class="rg">
|
||||
<input id="f-from" type="number" min="1" placeholder="da">
|
||||
<span class="dim">–</span>
|
||||
<input id="f-to" type="number" min="1" placeholder="a">
|
||||
</div>
|
||||
<input id="f-q" type="search" placeholder="Cerca nome o alleanza…">
|
||||
<button id="f-apply">Filtra</button>
|
||||
<button id="f-reset">Azzera</button>
|
||||
<span id="f-msg"></span>
|
||||
</section>
|
||||
|
||||
<div id="rk-count"></div>
|
||||
<div id="rk-results"></div>
|
||||
|
||||
<footer class="dim">Classifiche aggiornate ogni ora · API pubblica OGame · icone: <a href="https://game-icons.net" target="_blank" rel="noopener">game-icons.net</a> (CC BY 3.0)</footer>
|
||||
</div>
|
||||
|
||||
<script src="/static/icons.js?v=1"></script>
|
||||
<script src="/static/common.js?v=5"></script>
|
||||
<script src="/static/rankings.js?v=5"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,130 @@
|
||||
/* Classifiche — logica pagina */
|
||||
"use strict";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const S = { type: "total", status: "all", q: "", from: 0, to: 0 };
|
||||
|
||||
const STATUS_LABEL = { "": "Attivo", i: "Inattivo", I: "Inattivo lungo", v: "In vacanza",
|
||||
vi: "Vacanza + inatt.", vI: "Vacanza + inatt.", a: "Admin" };
|
||||
const stClass = (s) => "st-" + (s || "");
|
||||
|
||||
async function initHead() {
|
||||
try {
|
||||
const b = await api("/api/bootstrap");
|
||||
$("rk-universe").textContent = `${b.server.name} — Classifiche`;
|
||||
$("rk-sub").textContent =
|
||||
`[s${b.server.number}-${b.server.community}] · aggiornamento: ${b.fetched.highscore_total ? b.fetched.highscore_total : "—"} UTC`;
|
||||
} catch (e) { /* header non bloccante */ }
|
||||
}
|
||||
|
||||
function fmtScore(v) {
|
||||
if (v === null || v === undefined) return "—";
|
||||
return Number(v).toLocaleString("it-IT");
|
||||
}
|
||||
|
||||
function posClass(rank) {
|
||||
if (!rank) return "pos na";
|
||||
return "pos p" + rank;
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const msg = $("f-msg");
|
||||
msg.textContent = "Caricamento…";
|
||||
const p = new URLSearchParams({ type: S.type, status: S.status, q: S.q });
|
||||
if (S.from > 0) p.set("start", S.from);
|
||||
if (S.to > 0) p.set("end", S.to);
|
||||
try {
|
||||
const d = await api("/api/rankings?" + p.toString());
|
||||
render(d);
|
||||
msg.textContent = "";
|
||||
} catch (e) {
|
||||
msg.textContent = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
function render(d) {
|
||||
const isFleet = d.type === "fleet";
|
||||
const kindLabel = {
|
||||
total: "classifica generale",
|
||||
military: "classifica armamenti",
|
||||
fleet: "top flotte — navi dai rapporti",
|
||||
}[d.type] || d.type;
|
||||
$("rk-count").innerHTML = `Giocatori: <b>${d.count}</b> — ` +
|
||||
`<span class="dim">${kindLabel}</span>`;
|
||||
const rows = d.rows.map((r) => {
|
||||
const st = r.status || "";
|
||||
const stLab = STATUS_LABEL[st] || st;
|
||||
const jump = r.avatar ? `onclick="goGalaxy('${r.avatar}')"` : "";
|
||||
const mine = r.mine ? ' <span title="sei tu" class="mymark">●</span>' : "";
|
||||
const empty = r.avatar
|
||||
? `<span class="rk-avatar" ${jump} title="Vai al pianeta ${esc(r.avatar)}">${artSVG(r.avatar, 26)}</span>`
|
||||
: `<span class="rk-avatar">${artSVG(r.name + r.id, 26)}</span>`;
|
||||
const rep = r.reports || 0;
|
||||
const repCoords = r.report_coords || r.avatar;
|
||||
const repCell = rep
|
||||
? `<span class="rep-badge"${repCoords ? ` onclick="goGalaxy('${esc(repCoords)}')"` : ""} ` +
|
||||
`title="${rep} rapport${rep === 1 ? "o" : "i"} di spionaggio su questo giocatore">` +
|
||||
`${icon("badge_spy", 12)} ${rep}</span>`
|
||||
: `<span class="dim">—</span>`;
|
||||
return `<tr class="${r.mine ? "mine" : ""}">` +
|
||||
`<td class="${posClass(r.rank)}">${r.rank ? "#" + r.rank : "—"}</td>` +
|
||||
`<td>${empty}</td>` +
|
||||
`<td><span class="rk-name ${stClass(st)}" ${jump} title="Vai al pianeta">${esc(r.name)}</span>${mine}</td>` +
|
||||
`<td class="rk-ally">${r.alliance_tag ? "[" + esc(r.alliance_tag) + "]" : ""}</td>` +
|
||||
`<td><span class="pname-status ${stClass(st)}">${esc(stLab)}</span></td>` +
|
||||
`<td class="planets-n">${r.planets || 0}</td>` +
|
||||
`<td style="text-align:center">${repCell}</td>` +
|
||||
`<td class="score">${fmtScore(r.score)}<small>${r.score ? (isFleet ? "navi" : "punti") : ""}</small></td>` +
|
||||
`</tr>`;
|
||||
}).join("");
|
||||
const head =
|
||||
`<thead><tr>` +
|
||||
`<th style="width:56px">Pos</th><th style="width:40px"></th>` +
|
||||
`<th>Giocatore</th><th>Alleanza</th><th>Stato</th>` +
|
||||
`<th style="text-align:center">Pianeti</th><th style="text-align:center">Spie</th>` +
|
||||
`<th style="text-align:right">${isFleet ? "Navi" : "Punti"}</th>` +
|
||||
`</tr></thead>`;
|
||||
$("rk-results").innerHTML =
|
||||
rows
|
||||
? `<table class="rank-table">${head}<tbody>${rows}</tbody></table>`
|
||||
: `<div class="empty-note">Nessun giocatore corrisponde ai filtri.</div>`;
|
||||
}
|
||||
|
||||
function goGalaxy(coords) {
|
||||
const [g, s] = coords.split(":").map(Number);
|
||||
location.href = `/?g=${g}&s=${s}`;
|
||||
}
|
||||
|
||||
/* ---------------- eventi ---------------- */
|
||||
const TABS = { total: ["trophy", "Generale"], military: ["swords", "Armamenti"], fleet: ["cat_ships", "Top Flotte"] };
|
||||
$("rk-tabs").querySelectorAll(".rk-tab").forEach((t) => {
|
||||
const [ic, label] = TABS[t.dataset.type] || ["trophy", t.dataset.type];
|
||||
t.innerHTML = icon(ic, 15) + " " + label;
|
||||
t.onclick = () => {
|
||||
$("rk-tabs").querySelectorAll(".rk-tab").forEach((x) => x.classList.remove("on"));
|
||||
t.classList.add("on");
|
||||
S.type = t.dataset.type;
|
||||
load();
|
||||
};
|
||||
});
|
||||
function readFilters() {
|
||||
S.status = $("f-status").value;
|
||||
S.from = parseInt($("f-from").value, 10) || 0;
|
||||
S.to = parseInt($("f-to").value, 10) || 0;
|
||||
S.q = $("f-q").value.trim();
|
||||
}
|
||||
$("f-apply").onclick = () => { readFilters(); load(); };
|
||||
$("f-reset").onclick = () => {
|
||||
$("f-status").value = "all"; $("f-from").value = ""; $("f-to").value = ""; $("f-q").value = "";
|
||||
readFilters(); load();
|
||||
};
|
||||
["f-q", "f-from", "f-to"].forEach((id) => {
|
||||
$(id).addEventListener("keydown", (e) => { if (e.key === "Enter") { readFilters(); load(); } });
|
||||
});
|
||||
|
||||
/* ---------------- init ---------------- */
|
||||
initStars();
|
||||
const lg = document.getElementById("rk-logo");
|
||||
if (lg) lg.innerHTML = icon("trophy", 20);
|
||||
initHead();
|
||||
load();
|
||||
@@ -0,0 +1,498 @@
|
||||
/* OGame Galaxy Viewer v2 — stile spaziale, ispirato a OGame/galaxytool */
|
||||
:root {
|
||||
--bg0: #04060c;
|
||||
--bg1: #0a0f1a;
|
||||
--bg2: #101827;
|
||||
--bg3: #17223a;
|
||||
--panel: rgba(16, 24, 39, .86);
|
||||
--line: #26344e;
|
||||
--line-soft: #1b2740;
|
||||
--text: #d6e0f2;
|
||||
--text-dim: #7483a0;
|
||||
--accent: #57a6ff;
|
||||
--green: #52d273;
|
||||
--amber: #f0c94e;
|
||||
--orange: #ff9a3d;
|
||||
--red: #ff5d5d;
|
||||
--blue: #7ab8ff;
|
||||
--radius: 10px;
|
||||
--shadow: 0 8px 30px rgba(0,0,0,.55);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html, body {
|
||||
margin: 0; padding: 0;
|
||||
background: var(--bg0);
|
||||
color: var(--text);
|
||||
font-family: "Segoe UI", "Helvetica Neue", Verdana, Arial, sans-serif;
|
||||
font-size: 13.5px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
/* ---------- sfondo spaziale ---------- */
|
||||
#space {
|
||||
position: fixed; inset: 0; width: 100%; height: 100%;
|
||||
z-index: 0; pointer-events: none;
|
||||
}
|
||||
body::before {
|
||||
content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
|
||||
background:
|
||||
radial-gradient(900px 420px at 18% -8%, rgba(40,90,170,.20), transparent 60%),
|
||||
radial-gradient(800px 400px at 85% 110%, rgba(120,60,190,.13), transparent 60%),
|
||||
radial-gradient(1000px 600px at 60% 30%, rgba(20,40,80,.18), transparent 55%);
|
||||
}
|
||||
|
||||
#app {
|
||||
position: relative; z-index: 1;
|
||||
max-width: 1120px; margin: 0 auto; padding: 18px 16px 40px;
|
||||
}
|
||||
|
||||
.dim { color: var(--text-dim); }
|
||||
.hidden { display: none !important; }
|
||||
|
||||
/* ---------- header ---------- */
|
||||
#topbar {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
gap: 12px; flex-wrap: wrap;
|
||||
background: linear-gradient(180deg, rgba(30,44,74,.95), rgba(13,20,36,.92));
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 12px 16px;
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
.brand { display: flex; align-items: center; gap: 11px; font-weight: 700; font-size: 16px; letter-spacing: .3px; }
|
||||
.logo {
|
||||
width: 34px; height: 34px; border-radius: 50%;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
background: radial-gradient(circle at 32% 30%, #6fb1ff, #1a3a6b 70%);
|
||||
color: #fff; font-size: 17px;
|
||||
box-shadow: 0 0 0 1px rgba(120,180,255,.5), 0 0 18px rgba(60,140,255,.35);
|
||||
}
|
||||
#universe-sub { font-size: 11px; font-weight: 400; opacity: .8; display:block; margin-top:2px;}
|
||||
|
||||
.tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.nav-group {
|
||||
display: flex; align-items: center; gap: 4px;
|
||||
background: rgba(6,10,18,.6); border: 1px solid var(--line);
|
||||
border-radius: 8px; padding: 4px 8px;
|
||||
}
|
||||
.nav-group label { display: flex; align-items: center; gap: 3px; font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .4px;}
|
||||
.nav-group input {
|
||||
width: 54px; background: #0b1220; color: var(--text);
|
||||
border: 1px solid #2b3b5c; border-radius: 6px;
|
||||
padding: 5px 4px; text-align: center; font-size: 13px; font-weight: 600;
|
||||
}
|
||||
.nav-group input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgba(87,166,255,.25); }
|
||||
button {
|
||||
background: linear-gradient(180deg, #2c3f63, #1b2a47);
|
||||
color: var(--text); border: 1px solid #33466e;
|
||||
border-radius: 8px; padding: 6px 13px; cursor: pointer; font-size: 13px;
|
||||
transition: filter .15s, transform .05s;
|
||||
}
|
||||
button:hover { filter: brightness(1.25); }
|
||||
button:active { transform: translateY(1px); }
|
||||
button:disabled { opacity: .5; cursor: default; }
|
||||
#btn-refresh { padding: 6px 10px; font-size: 15px; line-height: 1; }
|
||||
.toplink {
|
||||
background: linear-gradient(180deg,#2c3f63,#1b2a47);
|
||||
border: 1px solid #33466e; border-radius: 8px;
|
||||
color: var(--text); padding: 6px 13px; text-decoration: none; font-size: 13px;
|
||||
}
|
||||
.toplink:hover { filter: brightness(1.25); color: #fff; }
|
||||
.toplink.back { color: var(--text-dim); }
|
||||
|
||||
/* ---------- meta / import ---------- */
|
||||
#meta-bar {
|
||||
display: flex; justify-content: space-between; gap: 10px;
|
||||
padding: 10px 6px 4px; font-size: 11px; flex-wrap: wrap;
|
||||
}
|
||||
#nav-status { color: var(--accent); font-size: 12px; font-weight: 600; }
|
||||
#meta-reports b { color: var(--amber); }
|
||||
|
||||
#import-box {
|
||||
display: flex; gap: 8px; align-items: center;
|
||||
background: var(--panel);
|
||||
border: 1px dashed #33466e;
|
||||
border-radius: var(--radius);
|
||||
padding: 9px 12px; margin: 6px 0 16px;
|
||||
flex-wrap: wrap;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,.3);
|
||||
}
|
||||
#import-box .lbl { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing:.5px; }
|
||||
#import-box input {
|
||||
flex: 1 1 340px; background: #070c16; color: var(--text);
|
||||
border: 1px solid #2b3b5c; border-radius: 7px; padding: 7px 11px;
|
||||
font-family: "Cascadia Mono", "Courier New", monospace; font-size: 12px;
|
||||
}
|
||||
#import-box input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgba(87,166,255,.22); }
|
||||
#import-msg { font-size: 12px; }
|
||||
#import-msg.ok { color: var(--green); }
|
||||
#import-msg.err { color: var(--red); }
|
||||
#btn-import { background: linear-gradient(180deg,#2e7dd1,#1d5aa6); border-color:#3f92e8; font-weight:600; }
|
||||
|
||||
/* niente freccette sugli input numerici (solo digitazione) */
|
||||
input[type="number"]::-webkit-outer-spin-button,
|
||||
input[type="number"]::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
/* ---------- galassia ---------- */
|
||||
#galaxy-wrap { overflow-x: auto; }
|
||||
#galaxy {
|
||||
width: 100%; border-collapse: separate; border-spacing: 0;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
#galaxy thead th {
|
||||
background: linear-gradient(180deg, rgba(36,52,86,.9), rgba(22,32,56,.9));
|
||||
color: #a9bcde; font-weight: 700; font-size: 10.5px;
|
||||
text-transform: uppercase; letter-spacing: 1px;
|
||||
padding: 9px 10px; text-align: left;
|
||||
border-bottom: 1px solid #2c3f63;
|
||||
}
|
||||
#galaxy tbody td {
|
||||
padding: 5px 10px; vertical-align: middle;
|
||||
border-bottom: 1px solid rgba(38,52,78,.45);
|
||||
font-size: 12.5px;
|
||||
}
|
||||
#galaxy tbody tr:last-child td { border-bottom: none; }
|
||||
#galaxy tbody tr:nth-child(even) td { background: rgba(255,255,255,.02); }
|
||||
#galaxy tbody tr:hover td { background: rgba(46,78,133,.22); }
|
||||
|
||||
td.c-pos { width: 40px; color: var(--text-dim); font-weight: 700; text-align:center; }
|
||||
td.c-rep { width: 84px; }
|
||||
|
||||
.pcell { display: flex; align-items: center; gap: 9px; cursor: pointer; padding: 2px 4px; border-radius: 8px; }
|
||||
.pcell:hover { background: rgba(70,110,180,.25); box-shadow: inset 0 0 0 1px rgba(87,166,255,.35); }
|
||||
.pcell .p-avatar { flex: 0 0 auto; filter: drop-shadow(0 2px 4px rgba(0,0,0,.6)); }
|
||||
.pname { font-weight: 700; color: #cfe1ff; letter-spacing:.2px; }
|
||||
.ro-tag {
|
||||
display:inline-block; margin-left:5px; padding:0 5px; vertical-align:1px;
|
||||
font-size:9px; text-transform:uppercase; letter-spacing:.4px;
|
||||
color:#c9a24a; border:1px solid rgba(201,162,74,.5); border-radius:8px;
|
||||
}
|
||||
.pcoords { color: var(--text-dim); font-size: 10px; }
|
||||
.planet-mine .pname { color: var(--green); }
|
||||
.planet-mine .pname::after { content: " ●"; color: var(--green); font-size: 9px; vertical-align: top; }
|
||||
.mymark { color: var(--green); font-size: 9px; vertical-align: top; }
|
||||
|
||||
.mcell { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; padding: 2px 6px; border-radius: 6px; color: var(--text-dim); }
|
||||
.mcell:hover { background: rgba(70,110,180,.2); }
|
||||
|
||||
.pname-status { font-weight: 600; }
|
||||
.pname-status.player-link { cursor: pointer; }
|
||||
.pname-status.player-link:hover { text-decoration: underline; filter: brightness(1.18); }
|
||||
.pranks {
|
||||
margin-left: 8px; font-size: 10.5px; color: var(--text-dim);
|
||||
white-space: nowrap; vertical-align: middle;
|
||||
}
|
||||
.pranks .rk { display: inline-block; margin-right: 7px; letter-spacing:.3px; }
|
||||
|
||||
/* ---------- pagina classifiche ---------- */
|
||||
#rk-head { margin: 0 0 4px; }
|
||||
.rk-tabs { display: inline-flex; background: rgba(10,15,26,.7); border: 1px solid var(--line); border-radius: 10px; padding: 3px; gap: 3px; }
|
||||
.rk-tab {
|
||||
border: none; background: none; color: var(--text-dim); font-weight: 700;
|
||||
border-radius: 8px; padding: 8px 18px; font-size: 13px; cursor: pointer;
|
||||
}
|
||||
.rk-tab.on { background: linear-gradient(180deg,#2c3f63,#1b2a47); color: #fff; }
|
||||
.rk-tab:hover:not(.on) { color: #fff; }
|
||||
|
||||
#rk-filter {
|
||||
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
|
||||
background: var(--panel); border: 1px solid var(--line);
|
||||
border-radius: var(--radius); padding: 10px 12px; margin: 12px 0 6px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,.3);
|
||||
}
|
||||
#rk-filter .lbl { font-size: 10.5px; color: var(--text-dim); text-transform: uppercase; letter-spacing:.5px; }
|
||||
#rk-filter input, #rk-filter select {
|
||||
background: #070c16; color: var(--text); border: 1px solid #2b3b5c;
|
||||
border-radius: 7px; padding: 6px 8px; font-size: 12.5px;
|
||||
}
|
||||
#rk-filter input:focus, #rk-filter select:focus { outline:none; border-color: var(--accent); }
|
||||
#rk-filter .rg { display:flex; align-items:center; gap:5px; }
|
||||
#rk-filter input[type=number] { width: 70px; }
|
||||
#rk-filter input[type=search] { width: 190px; }
|
||||
#rk-count { font-size: 11.5px; color: var(--text-dim); margin: 8px 4px 4px; }
|
||||
#rk-count b { color: var(--amber); }
|
||||
|
||||
#rk-results { overflow-x: auto; margin-top: 4px; }
|
||||
table.rank-table {
|
||||
width: 100%; border-collapse: separate; border-spacing: 0;
|
||||
background: var(--panel); border: 1px solid var(--line);
|
||||
border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow);
|
||||
}
|
||||
table.rank-table thead th {
|
||||
background: linear-gradient(180deg, rgba(36,52,86,.9), rgba(22,32,56,.9));
|
||||
color: #a9bcde; font-size: 10.5px; text-transform: uppercase; letter-spacing: 1px;
|
||||
padding: 9px 12px; text-align: left; border-bottom: 1px solid #2c3f63;
|
||||
}
|
||||
table.rank-table td {
|
||||
padding: 6px 12px; border-bottom: 1px solid rgba(38,52,78,.4); font-size: 12.5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
table.rank-table tr:nth-child(even) td { background: rgba(255,255,255,.02); }
|
||||
table.rank-table tbody tr:hover td { background: rgba(46,78,133,.22); }
|
||||
table.rank-table tr:last-child td { border-bottom: none; }
|
||||
table.rank-table tr.mine td { background: rgba(87,166,255,.10); }
|
||||
|
||||
.pos { font-weight: 800; text-align:center; width: 56px; }
|
||||
.pos.p1 { color: #f7d154; text-shadow: 0 0 8px rgba(247,209,84,.4); }
|
||||
.pos.p2 { color: #cfd6e4; }
|
||||
.pos.p3 { color: #e09b62; }
|
||||
.pos.na { color: #56617a; }
|
||||
|
||||
.rk-avatar { cursor: pointer; filter: drop-shadow(0 1px 3px rgba(0,0,0,.6)); }
|
||||
.rk-avatar:hover { filter: brightness(1.2) drop-shadow(0 0 6px rgba(87,166,255,.5)); }
|
||||
.rk-name { font-weight: 700; cursor: pointer; }
|
||||
.rk-name:hover { color: var(--accent); text-decoration: underline; }
|
||||
.rk-ally { color: #8fa3c7; font-size: 11.5px; }
|
||||
.score { font-weight: 700; color: #eef3ff; text-align:right; }
|
||||
.score small { display:block; color: var(--text-dim); font-weight:400; font-size:10px; }
|
||||
.planets-n { color: var(--text-dim); text-align:center; }
|
||||
|
||||
/* ---------- colori stato giocatore (galaxy OGame) ---------- */
|
||||
.st- { color: #f7d154; } /* attivo */
|
||||
.st-i, .st-I, .st-vi, .st-vI { color: #9aa4b5; } /* inattivo / vacanza+inattivo */
|
||||
.st-v { color: #6ec6ff; } /* in vacanza (celeste) */
|
||||
.st-a { color: #ff5555; } /* admin / staff */
|
||||
|
||||
.ally-tag { color: #8fa3c7; font-size: 11.5px; }
|
||||
|
||||
.rep-badge {
|
||||
display: inline-flex; align-items: center; gap: 5px;
|
||||
background: rgba(210,45,45,.9); color: #fff;
|
||||
border-radius: 20px; padding: 2px 10px; font-size: 11px; font-weight: 700;
|
||||
cursor: pointer; border: 1px solid #e26a66;
|
||||
box-shadow: 0 1px 6px rgba(210,45,45,.5);
|
||||
transition: filter .15s;
|
||||
}
|
||||
.rep-badge:hover { filter: brightness(1.2); }
|
||||
.rep-badge.rep-moon { background: rgba(98,74,158,.95); border-color:#a58bd6; box-shadow: 0 1px 6px rgba(98,74,158,.5); }
|
||||
.rep-badge .ico { font-size: 12px; }
|
||||
|
||||
/* ---------- popup ---------- */
|
||||
#overlay {
|
||||
position: fixed; inset: 0; z-index: 50;
|
||||
background: rgba(2,4,10,.82);
|
||||
display: flex; align-items: flex-start; justify-content: center;
|
||||
padding: 30px 12px; overflow-y: auto;
|
||||
backdrop-filter: blur(3px);
|
||||
animation: fadeIn .15s ease;
|
||||
}
|
||||
@keyframes fadeIn { from { opacity: 0 } to { opacity: 1 } }
|
||||
#popup {
|
||||
width: 920px; max-width: 100%;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 24px 80px rgba(0,0,0,.75);
|
||||
animation: popIn .18s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
@keyframes popIn { from { transform: translateY(14px); opacity: 0 } to { transform: none; opacity: 1 } }
|
||||
|
||||
#popup-head {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
background: linear-gradient(180deg, rgba(32,46,80,.96), rgba(16,24,42,.95));
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 12px 18px;
|
||||
}
|
||||
#popup-head .avatar { flex: 0 0 auto; filter: drop-shadow(0 3px 8px rgba(0,0,0,.6)); }
|
||||
#popup-title { font-weight: 800; font-size: 16px; letter-spacing:.3px; line-height: 1.25; }
|
||||
#popup-sub { font-size: 11.5px; color: var(--text-dim); margin-top: 2px; }
|
||||
.spacer { flex: 1 1 auto; }
|
||||
.icon-btn {
|
||||
background: rgba(255,255,255,.05); border: 1px solid var(--line);
|
||||
border-radius: 8px; width: 32px; height: 32px; flex: 0 0 auto; padding: 0;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
font-size: 15px; color: var(--text-dim); cursor: pointer;
|
||||
}
|
||||
.icon-btn:hover { color: #fff; background: rgba(255,255,255,.1); }
|
||||
|
||||
#popup-body { padding: 6px 18px 22px; max-height: 76vh; overflow-y: auto; }
|
||||
#popup-body::-webkit-scrollbar { width: 9px; }
|
||||
#popup-body::-webkit-scrollbar-thumb { background: #2b3b5c; border-radius: 8px; }
|
||||
|
||||
/* chips informazioni giocatore */
|
||||
.kv { display: flex; flex-wrap: wrap; gap: 8px 10px; margin: 10px 0 4px; }
|
||||
.chip {
|
||||
display: inline-flex; align-items: center; gap: 7px;
|
||||
background: rgba(255,255,255,.045);
|
||||
border: 1px solid var(--line-soft); border-radius: 20px;
|
||||
padding: 5px 12px; font-size: 12px;
|
||||
}
|
||||
.chip .k { color: var(--text-dim); text-transform: uppercase; letter-spacing:.5px; font-size: 9.5px; }
|
||||
.chip .v { font-weight: 700; }
|
||||
|
||||
/* altri pianeti */
|
||||
.chip-other { cursor: pointer; transition: background .12s, border-color .12s; }
|
||||
.chip-other:hover { background: rgba(70,110,180,.25); border-color: var(--accent); }
|
||||
.chip-other svg { vertical-align: middle; }
|
||||
|
||||
/* ---------- sezioni ---------- */
|
||||
.sec { margin: 14px 0 4px; }
|
||||
.sec h4 {
|
||||
margin: 16px 0 8px; font-size: 11px; color: #8fa6cc; font-weight: 800;
|
||||
letter-spacing: 1.1px; text-transform: uppercase;
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
}
|
||||
.sec h4::after { content:""; flex:1; height:1px; background: linear-gradient(90deg, var(--line), transparent); }
|
||||
.count-pill {
|
||||
background: rgba(87,166,255,.15); color: var(--accent);
|
||||
border-radius: 20px; padding: 0 8px; font-size: 10px; font-weight:700;
|
||||
}
|
||||
|
||||
/* icone svg (icons.js) */
|
||||
.ic { display: inline-block; flex: none; vertical-align: -2px; }
|
||||
button svg, .icon-btn svg, .toplink svg, .logo svg, .lbl svg,
|
||||
.chip svg, .rk svg, .pranks svg, .rep-badge svg, .rpt-foot svg,
|
||||
.meta-dots svg, .warn svg { vertical-align: -2px; }
|
||||
|
||||
/* ---------- card rapporto ---------- */
|
||||
.rpt-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(255px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.rpt-card {
|
||||
background: linear-gradient(180deg, rgba(24,33,55,.85), rgba(13,19,33,.9));
|
||||
border: 1px solid var(--line-soft);
|
||||
border-radius: 12px;
|
||||
padding: 12px 13px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: transform .12s, border-color .15s, box-shadow .15s;
|
||||
}
|
||||
.rpt-card:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgba(87,166,255,.55);
|
||||
box-shadow: 0 8px 22px rgba(0,0,0,.45);
|
||||
}
|
||||
.rpt-card .top { display: flex; align-items: center; gap: 8px; }
|
||||
.rpt-date { font-weight: 700; font-size: 12.5px; }
|
||||
.rpt-kind {
|
||||
font-size: 9.5px; text-transform: uppercase; letter-spacing:.6px;
|
||||
border-radius: 5px; padding: 1px 6px; font-weight:700;
|
||||
}
|
||||
.rpt-kind.planet { background: rgba(87,166,255,.18); color: var(--blue); }
|
||||
.rpt-kind.moon { background: rgba(165,139,214,.18); color:#c9b5f0; }
|
||||
.rpt-body { margin: 8px 0 2px; }
|
||||
.rpt-target { font-weight: 700; color: #e8f0ff; }
|
||||
.rpt-coords { color: var(--text-dim); font-weight: 500; font-size: 11px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
||||
.rpt-from { color: var(--text-dim); font-size: 11.5px; }
|
||||
.rpt-foot {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-top: 9px; padding-top: 8px; border-top: 1px dashed var(--line-soft);
|
||||
color: var(--text-dim); font-size: 11px; flex-wrap: wrap; gap:4px;
|
||||
}
|
||||
.rpt-open { color: var(--accent); font-weight: 700; font-size: 11px; }
|
||||
.meta-dots { display:flex; gap:4px; align-items:center; }
|
||||
.meta-dots .m { min-width:15px; text-align:center; }
|
||||
.act-dot { display:inline-block; width:9px; height:9px; border-radius:50%; box-shadow:0 0 6px currentColor; }
|
||||
.act-0 { background:#ff3b30; color:#ff3b30; }
|
||||
.act-15 { background:#ff9500; color:#ff9500; }
|
||||
.act-30 { background:#ffd60a; color:#ffd60a; }
|
||||
.act-60 { background:#30d158; color:#30d158; }
|
||||
.act-none { background:#8e8e93; color:#8e8e93; }
|
||||
|
||||
/* dettaglio rapporto */
|
||||
.detail-head { display:flex; align-items:center; gap:14px; margin: 12px 0 4px; }
|
||||
.back-btn { display:inline-flex; align-items:center; gap:6px; background:none; border:1px solid var(--line); color:var(--text); border-radius:8px; padding:5px 11px; font-size:12px; cursor:pointer;}
|
||||
.back-btn:hover { border-color: var(--accent); color:#fff; }
|
||||
|
||||
/* azioni nel dettaglio rapporto */
|
||||
.rpt-actions { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin:4px 0 2px; }
|
||||
.del-btn {
|
||||
display:inline-flex; align-items:center; gap:6px;
|
||||
background: rgba(210,45,45,.12); border:1px solid rgba(226,106,102,.55);
|
||||
color:#ff8a80; border-radius:8px; padding:5px 11px; font-size:12px;
|
||||
font-weight:700; cursor:pointer; transition: background .12s, color .12s;
|
||||
}
|
||||
.del-btn:hover { background: rgba(210,45,45,.25); color:#ffb0a8; border-color:#e26a66; }
|
||||
.res-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
|
||||
gap: 8px; margin: 10px 0;
|
||||
}
|
||||
.res-tile {
|
||||
border-radius: 10px; padding: 9px 12px;
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
border: 1px solid var(--line-soft);
|
||||
background: rgba(255,255,255,.03);
|
||||
}
|
||||
.res-tile .r-ico { color: var(--rc, #9fb0cc); }
|
||||
.res-tile .r-ico svg { width: 21px; height: 21px; }
|
||||
.res-tile .r-lab { font-size: 9.5px; text-transform: uppercase; letter-spacing:.6px; color: var(--text-dim); }
|
||||
.res-tile .r-val { font-weight: 800; font-size: 15px; }
|
||||
.res-metal { --rc: #e3c5ff; }
|
||||
.res-metal .r-val { color:#e3c5ff; }
|
||||
.res-crystal { --rc: #7fd4ff; }
|
||||
.res-crystal .r-val { color:#7fd4ff; }
|
||||
.res-deuterium { --rc: #7bf0b8; }
|
||||
.res-deuterium .r-val { color:#7bf0b8; }
|
||||
.res-pop { --rc: #ffd9a0; }
|
||||
.res-pop .r-val { color:#ffd9a0; }
|
||||
.res-food { --rc: #c4f27b; }
|
||||
.res-food .r-val { color:#c4f27b; }
|
||||
|
||||
/* griglia elementi tecnici */
|
||||
.tech-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 7px;
|
||||
}
|
||||
.tcard {
|
||||
display: flex; align-items: center; gap: 9px;
|
||||
background: rgba(255,255,255,.035);
|
||||
border: 1px solid var(--line-soft);
|
||||
border-left: 3px solid var(--tc, #57a6ff);
|
||||
border-radius: 9px;
|
||||
padding: 6px 9px;
|
||||
transition: background .12s, transform .12s;
|
||||
}
|
||||
.tcard:hover { background: rgba(255,255,255,.07); transform: translateY(-1px); }
|
||||
.tcard .tico {
|
||||
width: 30px; height: 30px; border-radius: 8px; flex: 0 0 auto;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: color-mix(in srgb, var(--tc,#57a6ff) 22%, transparent);
|
||||
color: var(--tc, #57a6ff);
|
||||
}
|
||||
.tcard .tico svg { width: 19px; height: 19px; }
|
||||
.tcard .tname { font-size: 11.5px; line-height: 1.2; flex: 1 1 auto; color:#dbe6f7; }
|
||||
.tcard .tval { font-weight: 800; font-size: 14px; color: #fff; }
|
||||
.tcard .tval small { font-weight:400; color:var(--text-dim); font-size:9.5px; }
|
||||
.cat-buildings { --tc: #fbbf24; }
|
||||
.cat-research { --tc: #38bdf8; }
|
||||
.cat-ships { --tc: #c084fc; }
|
||||
.cat-defense { --tc: #34d399; }
|
||||
.cat-lfbuildings { --tc: #a3e635; }
|
||||
|
||||
.warn {
|
||||
color: var(--amber); font-size: 11.5px; margin: 6px 0;
|
||||
background: rgba(240,201,78,.08); border:1px solid rgba(240,201,78,.25);
|
||||
border-radius: 8px; padding: 6px 10px;
|
||||
}
|
||||
.empty-note {
|
||||
color: var(--text-dim); font-style: italic; padding: 10px 4px;
|
||||
border: 1px dashed var(--line-soft); border-radius: 10px; text-align:center;
|
||||
}
|
||||
|
||||
footer { text-align: center; margin-top: 22px; font-size: 11px; opacity:.7; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.kv { gap: 6px; }
|
||||
.rpt-grid { grid-template-columns: 1fr; }
|
||||
.tech-grid { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
|
||||
}
|
||||
Reference in New Issue
Block a user