Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
b9e0cccfe1
|
|||
|
934581772d
|
|||
|
7c8dd807da
|
|||
|
8c816e2e6b
|
|||
|
cc51e1c9a7
|
|||
|
ae5105f40e
|
|||
|
4a9b9dd40b
|
@@ -42,5 +42,3 @@ yarn-error.log*
|
||||
|
||||
source-bestiary/
|
||||
source-drops/
|
||||
site/server-status.json
|
||||
site/server-status.json.tmp
|
||||
|
||||
-114
@@ -1939,117 +1939,3 @@ def hardcore_leaderboard_points_combined():
|
||||
return jsonify(rows[:100])
|
||||
|
||||
# --- end Hardcore stats aggregator -------------------------------------------
|
||||
|
||||
|
||||
@app.get("/server-status")
|
||||
@app.get("/api/server-status")
|
||||
def public_server_status():
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
prometheus_url = os.environ.get("PROMETHEUS_URL", "http://5.0.0.20:9090").rstrip("/")
|
||||
|
||||
def prom_value(query):
|
||||
url = prometheus_url + "/api/v1/query?" + urllib.parse.urlencode({"query": query})
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=2.5) as resp:
|
||||
data = json.loads(resp.read().decode("utf-8"))
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
if data.get("status") != "success":
|
||||
return 0
|
||||
|
||||
total = 0.0
|
||||
for result in data.get("data", {}).get("result", []):
|
||||
value = result.get("value", [None, "0"])[1]
|
||||
try:
|
||||
total += float(value)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
return int(total)
|
||||
|
||||
def q(metric, labels):
|
||||
label_text = ",".join(f'{k}="{v}"' for k, v in labels.items())
|
||||
return f'sum({metric}{{{label_text}}}) or vector(0)'
|
||||
|
||||
def newserv(region, service, ship, version):
|
||||
return prom_value(q("pso_newserv_clients_connected", {
|
||||
"region": region,
|
||||
"service": service,
|
||||
"ship": ship,
|
||||
"version": version,
|
||||
}))
|
||||
|
||||
def adhoc(region, game):
|
||||
return prom_value(q("psppeeps_adhoc_connected_clients_by_product", {
|
||||
"region": region,
|
||||
"service": f"{region}-psppeeps-adhoc",
|
||||
"ship": "psp",
|
||||
"game": game,
|
||||
}))
|
||||
|
||||
us = {
|
||||
"alis_v2": newserv("us", "us-newserv-live", "live", "v2"),
|
||||
"alis_v3": newserv("us", "us-newserv-live", "live", "v3"),
|
||||
"alis_bb": newserv("us", "us-newserv-live", "live", "v4"),
|
||||
"abion_hcbb": newserv("us", "us-newserv-hardcore", "hardcore", "v4"),
|
||||
"adhoc_psp1": adhoc("us", "psp1"),
|
||||
"adhoc_psp2i": adhoc("us", "psp2i"),
|
||||
}
|
||||
|
||||
eu = {
|
||||
"palma_v2": newserv("eu", "eu-newserv-live", "live", "v2"),
|
||||
"palma_v3": newserv("eu", "eu-newserv-live", "live", "v3"),
|
||||
"palma_bb": newserv("eu", "eu-newserv-live", "live", "v4"),
|
||||
"aiedo_hcbb": newserv("eu", "eu-newserv-hardcore", "hardcore", "v4"),
|
||||
"adhoc_psp1": adhoc("eu", "psp1"),
|
||||
"adhoc_psp2i": adhoc("eu", "psp2i"),
|
||||
}
|
||||
|
||||
us_total = sum(us.values())
|
||||
eu_total = sum(eu.values())
|
||||
|
||||
return jsonify({
|
||||
"servers": [
|
||||
{
|
||||
"label": "US Server",
|
||||
"players": us_total,
|
||||
"ships": [
|
||||
{"label": "Alis", "rows": [
|
||||
{"label": "V2", "players": us["alis_v2"]},
|
||||
{"label": "V3", "players": us["alis_v3"]},
|
||||
{"label": "BB", "players": us["alis_bb"]},
|
||||
]},
|
||||
{"label": "Abion", "rows": [
|
||||
{"label": "HC/BB", "players": us["abion_hcbb"]},
|
||||
]},
|
||||
{"label": "AdHoc-US", "rows": [
|
||||
{"label": "PSP1", "players": us["adhoc_psp1"]},
|
||||
{"label": "PSP2i", "players": us["adhoc_psp2i"]},
|
||||
]},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": "EU Server",
|
||||
"players": eu_total,
|
||||
"ships": [
|
||||
{"label": "Palma", "rows": [
|
||||
{"label": "V2", "players": eu["palma_v2"]},
|
||||
{"label": "V3", "players": eu["palma_v3"]},
|
||||
{"label": "BB", "players": eu["palma_bb"]},
|
||||
]},
|
||||
{"label": "Aiedo", "rows": [
|
||||
{"label": "HC/BB", "players": eu["aiedo_hcbb"]},
|
||||
]},
|
||||
{"label": "AdHoc-EU", "rows": [
|
||||
{"label": "PSP1", "players": eu["adhoc_psp1"]},
|
||||
{"label": "PSP2i", "players": eu["adhoc_psp2i"]},
|
||||
]},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
PROMETHEUS_URL = os.environ.get("PROMETHEUS_URL", "http://5.0.0.20:9090").rstrip("/")
|
||||
SOURCE_URL = os.environ.get("SERVER_STATUS_SOURCE_URL", "").strip()
|
||||
OUTPUT_PATH = Path(os.environ.get("SERVER_STATUS_JSON", "site/server-status.json"))
|
||||
TIMEOUT_SECONDS = float(os.environ.get("PROMETHEUS_TIMEOUT_SECONDS", "5"))
|
||||
|
||||
def write_data(data):
|
||||
if not isinstance(data.get("servers"), list):
|
||||
raise SystemExit("server status JSON missing servers array")
|
||||
|
||||
data["generated_at"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
||||
|
||||
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
tmp_path = OUTPUT_PATH.with_name(OUTPUT_PATH.name + ".tmp")
|
||||
tmp_path.write_text(json.dumps(data, separators=(",", ":")) + "\n")
|
||||
os.replace(tmp_path, OUTPUT_PATH)
|
||||
print(f"wrote {OUTPUT_PATH}")
|
||||
|
||||
def prom_value(query):
|
||||
url = PROMETHEUS_URL + "/api/v1/query?" + urllib.parse.urlencode({"query": query})
|
||||
with urllib.request.urlopen(url, timeout=TIMEOUT_SECONDS) as response:
|
||||
body = json.loads(response.read().decode("utf-8"))
|
||||
|
||||
result = body.get("data", {}).get("result", [])
|
||||
if not result:
|
||||
return 0
|
||||
return int(float(result[0]["value"][1]))
|
||||
|
||||
def q(metric, labels):
|
||||
label_text = ",".join(f'{key}="{value}"' for key, value in labels.items())
|
||||
return f"sum({metric}{{{label_text}}}) or vector(0)"
|
||||
|
||||
def newserv(region, service, ship, version):
|
||||
return prom_value(q("pso_newserv_clients_connected", {
|
||||
"region": region,
|
||||
"service": service,
|
||||
"ship": ship,
|
||||
"version": version,
|
||||
}))
|
||||
|
||||
def adhoc(region, game):
|
||||
return prom_value(q("psppeeps_adhoc_connected_clients_by_product", {
|
||||
"region": region,
|
||||
"service": f"{region}-psppeeps-adhoc",
|
||||
"ship": "psp",
|
||||
"game": game,
|
||||
}))
|
||||
|
||||
def row(label, players):
|
||||
return {"label": label, "players": int(players)}
|
||||
|
||||
def main():
|
||||
if SOURCE_URL:
|
||||
with urllib.request.urlopen(SOURCE_URL, timeout=TIMEOUT_SECONDS) as response:
|
||||
write_data(json.loads(response.read().decode("utf-8")))
|
||||
return
|
||||
|
||||
us_alis_v2 = newserv("us", "us-newserv-live", "live", "v2")
|
||||
us_alis_v3 = newserv("us", "us-newserv-live", "live", "v3")
|
||||
us_alis_bb = newserv("us", "us-newserv-live", "live", "v4")
|
||||
us_abion_hcbb = newserv("us", "us-newserv-hardcore", "hardcore", "v4")
|
||||
us_adhoc_psp1 = adhoc("us", "psp1")
|
||||
us_adhoc_psp2i = adhoc("us", "psp2i")
|
||||
|
||||
eu_palma_v2 = newserv("eu", "eu-newserv-live", "live", "v2")
|
||||
eu_palma_v3 = newserv("eu", "eu-newserv-live", "live", "v3")
|
||||
eu_palma_bb = newserv("eu", "eu-newserv-live", "live", "v4")
|
||||
eu_aiedo_hcbb = newserv("eu", "eu-newserv-hardcore", "hardcore", "v4")
|
||||
eu_adhoc_psp1 = adhoc("eu", "psp1")
|
||||
eu_adhoc_psp2i = adhoc("eu", "psp2i")
|
||||
|
||||
write_data({
|
||||
"servers": [
|
||||
{
|
||||
"label": "US Server",
|
||||
"players": us_alis_v2 + us_alis_v3 + us_alis_bb + us_abion_hcbb + us_adhoc_psp1 + us_adhoc_psp2i,
|
||||
"ships": [
|
||||
{"label": "Alis", "rows": [row("V2", us_alis_v2), row("V3", us_alis_v3), row("BB", us_alis_bb)]},
|
||||
{"label": "Abion", "rows": [row("HC/BB", us_abion_hcbb)]},
|
||||
{"label": "AdHoc-US", "rows": [row("PSP1", us_adhoc_psp1), row("PSP2i", us_adhoc_psp2i)]},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": "EU Server",
|
||||
"players": eu_palma_v2 + eu_palma_v3 + eu_palma_bb + eu_aiedo_hcbb + eu_adhoc_psp1 + eu_adhoc_psp2i,
|
||||
"ships": [
|
||||
{"label": "Palma", "rows": [row("V2", eu_palma_v2), row("V3", eu_palma_v3), row("BB", eu_palma_bb)]},
|
||||
{"label": "Aiedo", "rows": [row("HC/BB", eu_aiedo_hcbb)]},
|
||||
{"label": "AdHoc-EU", "rows": [row("PSP1", eu_adhoc_psp1), row("PSP2i", eu_adhoc_psp2i)]},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+26
-4
@@ -27,7 +27,7 @@
|
||||
8: { hp: 2.00, atp: 1.08, exp: 2.00 },
|
||||
9: { hp: 2.50, atp: 1.09, exp: 2.50 },
|
||||
10: { hp: 3.00, atp: 1.10, exp: 3.00 },
|
||||
11: { hp: 4.00, atp: 1.10, exp: null },
|
||||
11: { hp: 4.00, atp: 1.10, exp: 3.00 },
|
||||
};
|
||||
|
||||
const COLUMN_GROUPS = {
|
||||
@@ -148,13 +148,25 @@
|
||||
}
|
||||
|
||||
function bpTier() {
|
||||
const tier = Number(qs("#bestiary-bp-tier")?.value || 0);
|
||||
const select = qs("#bestiary-tier");
|
||||
const raw = String(select?.value || "");
|
||||
const label = String(select?.selectedOptions?.[0]?.textContent || "");
|
||||
const match = raw.match(/(\d+)$/) || label.match(/(\d+)/);
|
||||
const tier = match ? Number(match[1]) : 0;
|
||||
return BP_TIERS[tier] || BP_TIERS[0];
|
||||
}
|
||||
|
||||
function xpBonusMultiplier() {
|
||||
return Number(qs("#bestiary-xp-bonus")?.value || 1);
|
||||
}
|
||||
|
||||
function displayValue(row, key) {
|
||||
const value = row[key];
|
||||
|
||||
if (key === "exp" && row.difficulty !== "Ultimate") {
|
||||
return Math.round(Number(value || 0) * xpBonusMultiplier());
|
||||
}
|
||||
|
||||
if (row.difficulty !== "Ultimate") {
|
||||
return value ?? "";
|
||||
}
|
||||
@@ -170,16 +182,22 @@
|
||||
}
|
||||
|
||||
if (key === "exp" && tier.exp !== null) {
|
||||
return Math.round(Number(value || 0) * tier.exp);
|
||||
return Math.round(Number(value || 0) * tier.exp * xpBonusMultiplier());
|
||||
}
|
||||
|
||||
return value ?? "";
|
||||
}
|
||||
|
||||
function hasAnyBestiaryValue(row) {
|
||||
const keys = ["hp", "atp", "dfp", "mst", "ata", "evp", "lck", "esp", "exp", "efr", "eic", "eth", "elt", "edk"];
|
||||
return keys.some((key) => Number(row[key] || 0) !== 0);
|
||||
}
|
||||
|
||||
function visibleRows() {
|
||||
const search = state.filters.search.trim().toLowerCase();
|
||||
|
||||
return state.rows.filter((row) => {
|
||||
if (!hasAnyBestiaryValue(row)) return false;
|
||||
if (state.filters.mode && row.mode !== state.filters.mode) return false;
|
||||
if (state.filters.episode && row.episode !== state.filters.episode) return false;
|
||||
if (state.filters.difficulty && row.difficulty !== state.filters.difficulty) return false;
|
||||
@@ -332,11 +350,15 @@
|
||||
renderTable();
|
||||
});
|
||||
|
||||
qs("#bestiary-bp-tier")?.addEventListener("change", () => {
|
||||
qs("#bestiary-tier")?.addEventListener("change", () => {
|
||||
state.page = 1;
|
||||
renderTable();
|
||||
});
|
||||
|
||||
qs("#bestiary-xp-bonus")?.addEventListener("change", () => {
|
||||
renderTable();
|
||||
});
|
||||
|
||||
qs("#bestiary-placeholder")?.addEventListener("click", (event) => {
|
||||
const pageButton = event.target.closest("[data-bestiary-page]");
|
||||
if (pageButton) {
|
||||
|
||||
+9
-17
@@ -51,22 +51,13 @@
|
||||
<option value="">All difficulties</option>
|
||||
</select>
|
||||
|
||||
<label for="bestiary-bp-tier">BP Tier</label>
|
||||
<select id="bestiary-bp-tier">
|
||||
<option value="0">No modifier</option>
|
||||
<option value="1">Brutal Peeps +1</option>
|
||||
<option value="2">Brutal Peeps +2</option>
|
||||
<option value="3">Brutal Peeps +3</option>
|
||||
<option value="4">Brutal Peeps +4</option>
|
||||
<option value="5">Brutal Peeps +5</option>
|
||||
<option value="6">Brutal Peeps +6</option>
|
||||
<option value="7">Brutal Peeps +7</option>
|
||||
<option value="8">Brutal Peeps +8</option>
|
||||
<option value="9">Brutal Peeps +9</option>
|
||||
<option value="10">Brutal Peeps +10</option>
|
||||
<option value="11">Brutal Peeps +11</option>
|
||||
<label for="bestiary-xp-bonus">XP Bonus</label>
|
||||
<select id="bestiary-xp-bonus">
|
||||
<option value="1">No modifier</option>
|
||||
<option value="5">5x</option>
|
||||
<option value="10">10x</option>
|
||||
</select>
|
||||
<p class="drops-field-note">Applies HP, ATP, and EXP modifiers to Ultimate rows only.</p>
|
||||
<p class="drops-field-note">Applies EXP multiplier only.</p>
|
||||
|
||||
<label for="bestiary-view">View</label>
|
||||
<select id="bestiary-view">
|
||||
@@ -80,7 +71,8 @@
|
||||
|
||||
<label for="bestiary-tier" data-bestiary-tier-wrap>BP Tier</label>
|
||||
<select id="bestiary-tier" data-bestiary-tier-wrap>
|
||||
<option>BP+1</option><option>BP+2</option><option>BP+3</option>
|
||||
|
||||
<option value="0">No modifier</option><option>BP+1</option><option>BP+2</option><option>BP+3</option>
|
||||
<option>BP+4</option><option>BP+5</option><option>BP+6</option>
|
||||
<option>BP+7</option><option>BP+8</option><option>BP+9</option>
|
||||
<option>BP+10</option><option>BP+11</option>
|
||||
@@ -122,6 +114,6 @@
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="bestiary-tables.js?v=bestiary-table-viewer-20260613-3" defer></script>
|
||||
<script src="bestiary-tables.js?v=hide-zero-bestiary-rows-1" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const top = rows.slice(0, 10);
|
||||
const top = rows.slice(0, 5);
|
||||
|
||||
if (!top.length) {
|
||||
list.innerHTML = `<li><span class="rank">1.</span><span>—</span></li>`;
|
||||
|
||||
+37
-45
@@ -8,9 +8,8 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css?v=home-status-two-boxes-2">
|
||||
<link rel="stylesheet" href="style.css?v=home-hardcore-preline-20260610-1">
|
||||
<script src="app.js?v=account-status-label-20260609" defer></script>
|
||||
<script src="server-status.js?v=home-status-prometheus-2" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="site-shell">
|
||||
@@ -53,48 +52,30 @@
|
||||
|
||||
<h1 id="server-status-heading" class="section-title">Server Status</h1>
|
||||
|
||||
<div class="status-list status-list--regions" role="list" aria-label="Current server player counts" data-server-status="loading">
|
||||
<div class="status-region" role="group" aria-label="US Server player counts">
|
||||
<div class="status-region-header">
|
||||
<span>US Server</span><span>Loading…</span>
|
||||
</div>
|
||||
<div class="status-ship">
|
||||
<div class="status-ship-name">Alis</div>
|
||||
<div class="status-row"><span>V2</span><span>—</span></div>
|
||||
<div class="status-row"><span>V3</span><span>—</span></div>
|
||||
<div class="status-row"><span>BB</span><span>—</span></div>
|
||||
</div>
|
||||
<div class="status-ship">
|
||||
<div class="status-ship-name">Abion</div>
|
||||
<div class="status-row"><span>HC/BB</span><span>—</span></div>
|
||||
</div>
|
||||
<div class="status-ship">
|
||||
<div class="status-ship-name">AdHoc-US</div>
|
||||
<div class="status-row"><span>PSP1</span><span>—</span></div>
|
||||
<div class="status-row"><span>PSP2i</span><span>—</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-list" role="list" aria-label="Current server player counts">
|
||||
<div class="status-row status-parent" role="listitem"><span>US Server</span><span></span></div>
|
||||
|
||||
<div class="status-region" role="group" aria-label="EU Server player counts">
|
||||
<div class="status-region-header">
|
||||
<span>EU Server</span><span>Loading…</span>
|
||||
</div>
|
||||
<div class="status-ship">
|
||||
<div class="status-ship-name">Palma</div>
|
||||
<div class="status-row"><span>V2</span><span>—</span></div>
|
||||
<div class="status-row"><span>V3</span><span>—</span></div>
|
||||
<div class="status-row"><span>BB</span><span>—</span></div>
|
||||
</div>
|
||||
<div class="status-ship">
|
||||
<div class="status-ship-name">Aiedo</div>
|
||||
<div class="status-row"><span>HC/BB</span><span>—</span></div>
|
||||
</div>
|
||||
<div class="status-ship">
|
||||
<div class="status-ship-name">AdHoc-EU</div>
|
||||
<div class="status-row"><span>PSP1</span><span>—</span></div>
|
||||
<div class="status-row"><span>PSP2i</span><span>—</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-row status-parent" role="listitem"><span>Alis</span><span>0 Players</span></div>
|
||||
<div class="status-row status-child" role="listitem"><span>V2</span><span>0 Players</span></div>
|
||||
<div class="status-row status-child" role="listitem"><span>V3</span><span>0 Players</span></div>
|
||||
<div class="status-row status-child" role="listitem"><span>BB</span><span>0 Players</span></div>
|
||||
|
||||
<div class="status-row status-parent" role="listitem"><span>Abion</span><span></span></div>
|
||||
<div class="status-row status-child" role="listitem"><span>HC</span><span>0 Players</span></div>
|
||||
|
||||
<div class="status-row status-parent" role="listitem"><span>EU Server</span><span></span></div>
|
||||
|
||||
<div class="status-row status-parent" role="listitem"><span>Palma</span><span>0 Players</span></div>
|
||||
<div class="status-row status-child" role="listitem"><span>V2</span><span>0 Players</span></div>
|
||||
<div class="status-row status-child" role="listitem"><span>V3</span><span>0 Players</span></div>
|
||||
<div class="status-row status-child" role="listitem"><span>BB</span><span>0 Players</span></div>
|
||||
|
||||
<div class="status-row status-parent" role="listitem"><span>Aiedo</span><span></span></div>
|
||||
<div class="status-row status-child" role="listitem"><span>HC</span><span>0 Players</span></div>
|
||||
|
||||
<div class="status-row status-parent" role="listitem"><span>PSP Ship</span><span></span></div>
|
||||
<div class="status-row status-child" role="listitem"><span>PSP1</span><span>0 Players</span></div>
|
||||
<div class="status-row status-child" role="listitem"><span>PSP2i</span><span>0 Players</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -109,11 +90,22 @@
|
||||
|
||||
<section class="card leaderboard-card" aria-labelledby="hardcore-heading">
|
||||
<h2 id="hardcore-heading" class="section-title">Hardcore Leaderboard</h2>
|
||||
<ol class="leaderboard-list leaderboard-list--home-hardcore" id="home-hardcore-leaderboard-body" aria-label="Top ten hardcore players">
|
||||
<ol class="leaderboard-list leaderboard-list--home-hardcore" id="home-hardcore-leaderboard-body" aria-label="Top five hardcore players">
|
||||
<li><span class="rank">1.</span><span>Loading...</span></li>
|
||||
</ol>
|
||||
<a class="small-link home-leaderboard-more" href="leaderboards.html">more</a>
|
||||
</section>
|
||||
|
||||
<section class="card leaderboard-card" aria-labelledby="crank-heading">
|
||||
<h2 id="crank-heading" class="section-title">C Rank Points</h2>
|
||||
<ol class="leaderboard-list" aria-label="Top five C Rank point totals">
|
||||
<li><span class="rank">1.</span><span>—</span></li>
|
||||
<li><span class="rank">2.</span><span>—</span></li>
|
||||
<li><span class="rank">3.</span><span>—</span></li>
|
||||
<li><span class="rank">4.</span><span>—</span></li>
|
||||
<li><span class="rank">5.</span><span>—</span></li>
|
||||
</ol>
|
||||
</section>
|
||||
</aside>
|
||||
</main>
|
||||
|
||||
@@ -147,6 +139,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
<script src="hero-cycle.js?v=force-cycle-3" defer></script>
|
||||
<script src="home-leaderboard.js?v=home-hardcore-top10-1" defer></script>
|
||||
<script src="home-leaderboard.js?v=home-hardcore-preline-20260610-1" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
(() => {
|
||||
const list = document.querySelector(".status-list");
|
||||
if (!list) return;
|
||||
|
||||
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (ch) => ({
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"\"": """,
|
||||
"'": "'",
|
||||
}[ch]));
|
||||
|
||||
const players = (value) => {
|
||||
const n = Number(value || 0);
|
||||
return `${n.toLocaleString()} ${n === 1 ? "Player" : "Players"}`;
|
||||
};
|
||||
|
||||
const render = (data) => {
|
||||
const servers = Array.isArray(data?.servers) ? data.servers : [];
|
||||
if (!servers.length) return;
|
||||
|
||||
list.classList.add("status-list--regions");
|
||||
list.innerHTML = servers.map((server) => `
|
||||
<div class="status-region" role="group" aria-label="${esc(server.label)} player counts">
|
||||
<div class="status-row status-parent status-server" role="listitem">
|
||||
<span>${esc(server.label)}</span><span>${players(server.players)}</span>
|
||||
</div>
|
||||
${(server.ships || []).map((ship) => `
|
||||
<div class="status-row status-parent status-ship" role="listitem">
|
||||
<span>${esc(ship.label)}</span><span></span>
|
||||
</div>
|
||||
${(ship.rows || []).map((row) => `
|
||||
<div class="status-row status-child" role="listitem">
|
||||
<span>${esc(row.label)}</span><span>${players(row.players)}</span>
|
||||
</div>
|
||||
`).join("")}
|
||||
`).join("")}
|
||||
</div>
|
||||
`).join("");
|
||||
};
|
||||
|
||||
const loadStatus = (url) => fetch(url, { cache: "no-store" })
|
||||
.then((res) => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`status ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
|
||||
loadStatus(`/server-status.json?ts=${Date.now()}`)
|
||||
.catch(() => loadStatus("/api/server-status"))
|
||||
.then((data) => data && render(data))
|
||||
.catch(() => {});
|
||||
})();
|
||||
@@ -1857,61 +1857,4 @@ button.inline-link,
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}\n\n
|
||||
/* Home right column: stretch Hardcore leaderboard after C Rank removal */
|
||||
.right-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.right-stack .leaderboard-card {
|
||||
flex: 1;
|
||||
}
|
||||
\n
|
||||
|
||||
|
||||
/* Home layout: stretch right column to match server status card */
|
||||
.home-grid {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.right-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.right-stack .leaderboard-card {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
|
||||
/* Home server status: split US/EU into two inner boxes */
|
||||
.server-card .status-list.status-list--regions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.server-card .status-region {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 0.45rem;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.server-card .status-region .status-row {
|
||||
grid-template-columns: minmax(0, 1fr) max-content;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.server-card .status-list.status-list--regions {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user