Switch web UI to dark English interface

This commit is contained in:
Knutwurst committed 2026-06-23 08:17:12 +02:00
1 parent 56f54fc1a0
commit 64bce0a3b5
4 files changed
+120 -119

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@ static const char status_json[] =
"{" "{"
"\"firmware\":\"11.60\"," "\"firmware\":\"11.60\","
"\"firmware_build\":\"0x11600000\"," "\"firmware_build\":\"0x11600000\","
"\"dns_guard\":\"Aktiv\"," "\"dns_guard\":\"Active\","
"\"resolver\":\"Internal allowlist\"," "\"resolver\":\"Internal allowlist\","
"\"free_space_mb\":64218," "\"free_space_mb\":64218,"
"\"download_dir\":\"/mnt/usb0/patches\"" "\"download_dir\":\"/mnt/usb0/patches\""
+44 -44
View File
@@ -11,13 +11,13 @@ const SOURCE_TYPES = {
label: "Official", label: "Official",
className: "ok", className: "ok",
installAllowed: true, installAllowed: true,
description: "Echte Installation", description: "Native console installation",
}, },
external: { external: {
label: "External", label: "External",
className: "external", className: "external",
installAllowed: true, installAllowed: true,
description: "Echte externe Installation", description: "Real external installation",
}, },
shadowmount: { shadowmount: {
label: "Shadowmount", label: "Shadowmount",
@@ -29,7 +29,7 @@ const SOURCE_TYPES = {
label: "Unknown", label: "Unknown",
className: "blocked", className: "blocked",
installAllowed: false, installAllowed: false,
description: "Blockiert", description: "Blocked",
}, },
}; };
@@ -37,7 +37,7 @@ const fallback = {
status: { status: {
firmware: "11.60", firmware: "11.60",
firmware_build: "0x11600000", firmware_build: "0x11600000",
dns_guard: "Aktiv", dns_guard: "Active",
resolver: "Internal allowlist", resolver: "Internal allowlist",
free_space_mb: 64218, free_space_mb: 64218,
download_dir: "/mnt/usb0/patches", download_dir: "/mnt/usb0/patches",
@@ -132,7 +132,7 @@ const fallback = {
name: "Demon's Souls", name: "Demon's Souls",
version: "01.004.000", version: "01.004.000",
progress: 64, progress: 64,
detail: "4.8 GB von 7.5 GB", detail: "4.8 GB of 7.5 GB",
}, },
], ],
logs: [ logs: [
@@ -204,7 +204,7 @@ function bindEvents() {
els.refreshBtn.addEventListener("click", loadInitialData); els.refreshBtn.addEventListener("click", loadInitialData);
els.saveBtn.addEventListener("click", saveConfig); els.saveBtn.addEventListener("click", saveConfig);
els.pauseAllBtn.addEventListener("click", () => showToast("Queue wurde pausiert.")); els.pauseAllBtn.addEventListener("click", () => showToast("Queue paused."));
} }
async function loadInitialData() { async function loadInitialData() {
@@ -224,7 +224,7 @@ async function loadInitialData() {
}; };
render(); render();
showToast(state.usingFallback ? "Demo-Daten geladen. API noch nicht erreichbar." : "Daten aktualisiert."); showToast(state.usingFallback ? "Demo data loaded. API is not reachable yet." : "Data refreshed.");
} }
async function getJson(url, fallbackValue) { async function getJson(url, fallbackValue) {
@@ -248,11 +248,11 @@ function render() {
function renderStatus() { function renderStatus() {
els.firmwareValue.textContent = state.status.firmware || "--"; els.firmwareValue.textContent = state.status.firmware || "--";
els.firmwareBuild.textContent = state.status.firmware_build || "Systemversion"; els.firmwareBuild.textContent = state.status.firmware_build || "System version";
els.dnsValue.textContent = state.status.dns_guard || "--"; els.dnsValue.textContent = state.status.dns_guard || "--";
els.resolverValue.textContent = state.status.resolver || "--"; els.resolverValue.textContent = state.status.resolver || "--";
els.spaceValue.textContent = formatMb(state.status.free_space_mb); els.spaceValue.textContent = formatMb(state.status.free_space_mb);
els.downloadDirValue.textContent = state.status.download_dir || state.config.download_dir || "Download Ziel"; els.downloadDirValue.textContent = state.status.download_dir || state.config.download_dir || "Download target";
} }
function renderSettings() { function renderSettings() {
@@ -276,7 +276,7 @@ function renderGames() {
if (!visible.length) { if (!visible.length) {
const empty = document.createElement("div"); const empty = document.createElement("div");
empty.className = "empty-state"; empty.className = "empty-state";
empty.textContent = "Keine Titel fuer diesen Filter."; empty.textContent = "No titles match this filter.";
els.gameGrid.appendChild(empty); els.gameGrid.appendChild(empty);
return; return;
} }
@@ -319,8 +319,8 @@ function createGameCard(game) {
<div class="subline" style="margin-top:8px"> <div class="subline" style="margin-top:8px">
${statusPill(game)} ${statusPill(game)}
${sourcePill(game)} ${sourcePill(game)}
${installBlocked ? `<span class="pill blocked">Install gesperrt</span>` : `<span class="pill ok">Install erlaubt</span>`} ${installBlocked ? `<span class="pill blocked">Install blocked</span>` : `<span class="pill ok">Install allowed</span>`}
<span class="pill">${game.enabled ? "Aktiv" : "Aus"}</span> <span class="pill">${game.enabled ? "Enabled" : "Off"}</span>
</div> </div>
<div class="source-path">${escapeHtml(sourceDetail(game))}</div> <div class="source-path">${escapeHtml(sourceDetail(game))}</div>
</div> </div>
@@ -329,9 +329,9 @@ function createGameCard(game) {
const versions = document.createElement("div"); const versions = document.createElement("div");
versions.className = "version-grid"; versions.className = "version-grid";
versions.innerHTML = ` versions.innerHTML = `
${versionBox("Installiert", game.installed_version || "--")} ${versionBox("Installed", game.installed_version || "--")}
${versionBox("Kompatibel", game.compatible_version || "Keine")} ${versionBox("Compatible", game.compatible_version || "None")}
${versionBox("Neueste", `${game.latest_version || "--"} / FW ${game.latest_required_fw || "--"}`)} ${versionBox("Latest", `${game.latest_version || "--"} / FW ${game.latest_required_fw || "--"}`)}
`; `;
const controls = document.createElement("div"); const controls = document.createElement("div");
@@ -341,7 +341,7 @@ function createGameCard(game) {
controlRow.className = "control-row"; controlRow.className = "control-row";
const mode = document.createElement("select"); const mode = document.createElement("select");
mode.title = "Update-Modus"; mode.title = "Update mode";
[ [
["disabled", "Disabled"], ["disabled", "Disabled"],
["download_only", "Download only"], ["download_only", "Download only"],
@@ -358,7 +358,7 @@ function createGameCard(game) {
mode.addEventListener("change", () => updateGame(game.title_id, { mode: mode.value, enabled: mode.value !== "disabled" })); mode.addEventListener("change", () => updateGame(game.title_id, { mode: mode.value, enabled: mode.value !== "disabled" }));
const pin = document.createElement("input"); const pin = document.createElement("input");
pin.title = "Maximale Content-Version"; pin.title = "Maximum content version";
pin.placeholder = "max ver"; pin.placeholder = "max ver";
pin.value = game.max_content_ver || ""; pin.value = game.max_content_ver || "";
pin.addEventListener("change", () => updateGame(game.title_id, { max_content_ver: pin.value.trim() })); pin.addEventListener("change", () => updateGame(game.title_id, { max_content_ver: pin.value.trim() }));
@@ -370,11 +370,11 @@ function createGameCard(game) {
actions.innerHTML = ` actions.innerHTML = `
<button class="row-button" data-action="check"> <button class="row-button" data-action="check">
<svg><use href="#icon-refresh"></use></svg> <svg><use href="#icon-refresh"></use></svg>
Pruefen Check
</button> </button>
<button class="row-button is-primary" data-action="download" ${!isDownloadAllowed(game) ? "disabled" : ""} title="${escapeHtml(downloadTitle(game))}"> <button class="row-button is-primary" data-action="download" ${!isDownloadAllowed(game) ? "disabled" : ""} title="${escapeHtml(downloadTitle(game))}">
<svg><use href="#icon-download"></use></svg> <svg><use href="#icon-download"></use></svg>
Laden Download
</button> </button>
`; `;
@@ -399,8 +399,8 @@ function versionBox(label, value) {
function statusPill(game) { function statusPill(game) {
if (game.status === "blocked") return `<span class="pill blocked">${escapeHtml(blockedLabel(game))}</span>`; if (game.status === "blocked") return `<span class="pill blocked">${escapeHtml(blockedLabel(game))}</span>`;
if (game.status === "queued") return `<span class="pill warn">In Queue</span>`; if (game.status === "queued") return `<span class="pill warn">In Queue</span>`;
if (game.status === "available") return `<span class="pill ok">Update verfuegbar</span>`; if (game.status === "available") return `<span class="pill ok">Update available</span>`;
return `<span class="pill">Aktuell</span>`; return `<span class="pill">Current</span>`;
} }
function sourcePill(game) { function sourcePill(game) {
@@ -418,16 +418,16 @@ function sourceType(game) {
function sourceDetail(game) { function sourceDetail(game) {
const src = sourceType(game); const src = sourceType(game);
if (src === "shadowmount") return `Mount: ${game.mount_from || "unbekannte Quelle"}`; if (src === "shadowmount") return `Mount: ${game.mount_from || "unknown source"}`;
if (src === "external") return `External: ${game.source_path || "externe App-Metadaten"}`; if (src === "external") return `External: ${game.source_path || "external app metadata"}`;
if (src === "official") return game.source_path || "Offizielle Installation"; if (src === "official") return game.source_path || "Official installation";
return "Quelle nicht eindeutig erkannt"; return "Source could not be identified";
} }
function blockedLabel(game) { function blockedLabel(game) {
if (sourceType(game) === "unknown") return "Quelle unbekannt"; if (sourceType(game) === "unknown") return "Unknown source";
if (!game.compatible_version) return "FW blockiert"; if (!game.compatible_version) return "FW blocked";
return "Blockiert"; return "Blocked";
} }
function renderQueue() { function renderQueue() {
@@ -435,7 +435,7 @@ function renderQueue() {
if (!state.downloads.length) { if (!state.downloads.length) {
const empty = document.createElement("div"); const empty = document.createElement("div");
empty.className = "empty-state"; empty.className = "empty-state";
empty.textContent = "Keine aktiven Downloads."; empty.textContent = "No active downloads.";
els.queueList.appendChild(empty); els.queueList.appendChild(empty);
return; return;
} }
@@ -463,8 +463,8 @@ function renderSourcePolicies() {
row.className = "policy-row"; row.className = "policy-row";
row.innerHTML = ` row.innerHTML = `
<span class="pill ${info.className}">${escapeHtml(info.label)}</span> <span class="pill ${info.className}">${escapeHtml(info.label)}</span>
<strong>${policy.allow_install ? "Install erlaubt" : "Install gesperrt"}</strong> <strong>${policy.allow_install ? "Install allowed" : "Install blocked"}</strong>
<em>${policy.allow_download ? "Download erlaubt" : "Download gesperrt"} - ${escapeHtml(info.description)}</em> <em>${policy.allow_download ? "Download allowed" : "Download blocked"} - ${escapeHtml(info.description)}</em>
`; `;
return row; return row;
}); });
@@ -487,10 +487,10 @@ async function saveConfig() {
try { try {
await postJson(API.config, config); await postJson(API.config, config);
state.config = config; state.config = config;
showToast("Konfiguration gespeichert."); showToast("Configuration saved.");
} catch (error) { } catch (error) {
state.config = config; state.config = config;
showToast("Konfiguration lokal aktualisiert. API noch nicht erreichbar."); showToast("Configuration updated locally. API is not reachable yet.");
} }
renderSettings(); renderSettings();
@@ -513,8 +513,8 @@ async function runTitleAction(titleId, action) {
if (action === "download" && game.compatible_version) { if (action === "download" && game.compatible_version) {
const detail = isInstallBlocked(game) const detail = isInstallBlocked(game)
? "Download only - Install durch Source-Policy gesperrt" ? "Download only - install blocked by source policy"
: "Wartet auf Start"; : "Waiting to start";
game.queued = true; game.queued = true;
game.status = "queued"; game.status = "queued";
if (!state.downloads.some((item) => item.title_id === titleId)) { if (!state.downloads.some((item) => item.title_id === titleId)) {
@@ -527,10 +527,10 @@ async function runTitleAction(titleId, action) {
}); });
} }
state.logs.push(`[${timeNow()}] Queued ${game.title_id} ${game.compatible_version} (${sourceType(game)}, install=${isInstallBlocked(game) ? "blocked" : "allowed"})`); state.logs.push(`[${timeNow()}] Queued ${game.title_id} ${game.compatible_version} (${sourceType(game)}, install=${isInstallBlocked(game) ? "blocked" : "allowed"})`);
showToast(`${game.title_id} wurde zur Queue hinzugefuegt.`); showToast(`${game.title_id} was added to the queue.`);
} else { } else {
state.logs.push(`[${timeNow()}] Check requested for ${game.title_id}`); state.logs.push(`[${timeNow()}] Check requested for ${game.title_id}`);
showToast(`${game.title_id} wird geprueft.`); showToast(`${game.title_id} check queued.`);
} }
renderGames(); renderGames();
@@ -544,7 +544,7 @@ function updateGame(titleId, patch) {
if (sourceType(game) === "unknown" && patch.mode && !["disabled", "check_only"].includes(patch.mode)) { if (sourceType(game) === "unknown" && patch.mode && !["disabled", "check_only"].includes(patch.mode)) {
patch.mode = "check_only"; patch.mode = "check_only";
patch.enabled = true; patch.enabled = true;
showToast(`${titleId}: Quelle unbekannt, nur Check only erlaubt.`); showToast(`${titleId}: unknown source, check only is allowed.`);
} }
Object.assign(game, patch); Object.assign(game, patch);
if (patch.mode === "disabled") game.enabled = false; if (patch.mode === "disabled") game.enabled = false;
@@ -573,11 +573,11 @@ function sourcePolicyForType(type) {
} }
function downloadTitle(game) { function downloadTitle(game) {
if (!game.enabled) return "Titel ist deaktiviert."; if (!game.enabled) return "Title is disabled.";
if (!game.compatible_version) return "Keine kompatible Update-Version verfuegbar."; if (!game.compatible_version) return "No compatible update version is available.";
if (!sourcePolicy(game).allow_download) return "Download ist fuer diese Quelle gesperrt."; if (!sourcePolicy(game).allow_download) return "Downloads are blocked for this source.";
if (isInstallBlocked(game)) return "Download erlaubt, Installation bleibt fuer diese Quelle gesperrt."; if (isInstallBlocked(game)) return "Download allowed; installs stay blocked for this source.";
return "Kompatibles Update laden."; return "Download compatible update.";
} }
async function postJson(url, body) { async function postJson(url, body) {
@@ -620,7 +620,7 @@ function clone(value) {
} }
function timeNow() { function timeNow() {
return new Date().toLocaleTimeString("de-DE", { hour12: false }); return new Date().toLocaleTimeString("en-US", { hour12: false });
} }
let toastTimer; let toastTimer;
+26 -26
View File
@@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html lang="de"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
@@ -60,7 +60,7 @@
<nav class="nav-list"> <nav class="nav-list">
<a class="nav-link is-active" href="#games"> <a class="nav-link is-active" href="#games">
<svg><use href="#icon-download"></use></svg> <svg><use href="#icon-download"></use></svg>
Spiele Games
</a> </a>
<a class="nav-link" href="#downloads"> <a class="nav-link" href="#downloads">
<svg><use href="#icon-refresh"></use></svg> <svg><use href="#icon-refresh"></use></svg>
@@ -81,54 +81,54 @@
<header class="topbar"> <header class="topbar">
<div> <div>
<p class="eyebrow">Standalone ELF Web UI</p> <p class="eyebrow">Standalone ELF Web UI</p>
<h1>Spielupdates kontrolliert laden</h1> <h1>Controlled game patch downloads</h1>
</div> </div>
<div class="topbar-actions"> <div class="topbar-actions">
<button class="icon-button" id="refreshBtn" title="Status und Spiele neu pruefen"> <button class="icon-button" id="refreshBtn" title="Refresh status and games">
<svg><use href="#icon-refresh"></use></svg> <svg><use href="#icon-refresh"></use></svg>
</button> </button>
<button class="primary-button" id="saveBtn"> <button class="primary-button" id="saveBtn">
<svg><use href="#icon-save"></use></svg> <svg><use href="#icon-save"></use></svg>
Speichern Save
</button> </button>
</div> </div>
</header> </header>
<section class="status-strip" aria-label="Systemstatus"> <section class="status-strip" aria-label="System status">
<article class="metric"> <article class="metric">
<span>Firmware</span> <span>Firmware</span>
<strong id="firmwareValue">--</strong> <strong id="firmwareValue">--</strong>
<em id="firmwareBuild">Systemversion</em> <em id="firmwareBuild">System version</em>
</article> </article>
<article class="metric"> <article class="metric">
<span>DNS Guard</span> <span>DNS Guard</span>
<strong id="dnsValue">--</strong> <strong id="dnsValue">--</strong>
<em>Sony blockiert per nanoDNS</em> <em>Sony blocked by nanoDNS</em>
</article> </article>
<article class="metric"> <article class="metric">
<span>CDN Zugriff</span> <span>CDN Access</span>
<strong id="resolverValue">--</strong> <strong id="resolverValue">--</strong>
<em>Nur PatchDL Resolver</em> <em>PatchDL resolver only</em>
</article> </article>
<article class="metric"> <article class="metric">
<span>Speicher</span> <span>Storage</span>
<strong id="spaceValue">--</strong> <strong id="spaceValue">--</strong>
<em id="downloadDirValue">Download Ziel</em> <em id="downloadDirValue">Download target</em>
</article> </article>
</section> </section>
<section class="toolbar" id="games"> <section class="toolbar" id="games">
<div class="search-box"> <div class="search-box">
<svg><use href="#icon-search"></use></svg> <svg><use href="#icon-search"></use></svg>
<input id="searchInput" type="search" placeholder="Titel, Title ID oder Content ID suchen" /> <input id="searchInput" type="search" placeholder="Search title, Title ID, or Content ID" />
</div> </div>
<div class="segmented" role="tablist" aria-label="Filter"> <div class="segmented" role="tablist" aria-label="Filter">
<button class="is-selected" data-filter="all">Alle</button> <button class="is-selected" data-filter="all">All</button>
<button data-filter="enabled">Aktiv</button> <button data-filter="enabled">Enabled</button>
<button data-filter="available">Updates</button> <button data-filter="available">Updates</button>
<button data-filter="shadowmount">Shadow</button> <button data-filter="shadowmount">Shadow</button>
<button data-filter="blocked">Blockiert</button> <button data-filter="blocked">Blocked</button>
<button data-filter="queued">Queue</button> <button data-filter="queued">Queue</button>
</div> </div>
</section> </section>
@@ -140,7 +140,7 @@
<div class="panel-heading"> <div class="panel-heading">
<div> <div>
<p class="eyebrow">Downloads</p> <p class="eyebrow">Downloads</p>
<h2>Aktive Queue</h2> <h2>Active Queue</h2>
</div> </div>
<button class="ghost-button" id="pauseAllBtn"> <button class="ghost-button" id="pauseAllBtn">
<svg><use href="#icon-pause"></use></svg> <svg><use href="#icon-pause"></use></svg>
@@ -154,7 +154,7 @@
<div class="panel-heading"> <div class="panel-heading">
<div> <div>
<p class="eyebrow">Policy</p> <p class="eyebrow">Policy</p>
<h2>Update Regeln</h2> <h2>Update Rules</h2>
</div> </div>
</div> </div>
@@ -167,32 +167,32 @@
</select> </select>
</label> </label>
<label class="field"> <label class="field">
<span>Download Ordner</span> <span>Download Folder</span>
<input id="downloadDir" type="text" spellcheck="false" /> <input id="downloadDir" type="text" spellcheck="false" />
</label> </label>
<label class="switch-row"> <label class="switch-row">
<input id="installAfterDownload" type="checkbox" /> <input id="installAfterDownload" type="checkbox" />
<span> <span>
<strong>Nach Download installieren</strong> <strong>Install after download</strong>
<em>Globaler Default, pro Spiel ueberschreibbar</em> <em>Global default, overridable per game</em>
</span> </span>
</label> </label>
<label class="switch-row"> <label class="switch-row">
<input id="deleteAfterInstall" type="checkbox" /> <input id="deleteAfterInstall" type="checkbox" />
<span> <span>
<strong>PKG nach Installation loeschen</strong> <strong>Delete PKG after install</strong>
<em>Nur wenn Installation erfolgreich war</em> <em>Only after a successful install</em>
</span> </span>
</label> </label>
</div> </div>
<div class="allowlist"> <div class="allowlist">
<span>Interne CDN Allowlist</span> <span>Internal CDN Allowlist</span>
<div id="allowlistHosts"></div> <div id="allowlistHosts"></div>
</div> </div>
<div class="source-policy"> <div class="source-policy">
<span>Install Schutz</span> <span>Install Protection</span>
<div id="sourcePolicyList"></div> <div id="sourcePolicyList"></div>
</div> </div>
</div> </div>
@@ -202,7 +202,7 @@
<div class="panel-heading"> <div class="panel-heading">
<div> <div>
<p class="eyebrow">Runtime</p> <p class="eyebrow">Runtime</p>
<h2>Letzte Ereignisse</h2> <h2>Recent Events</h2>
</div> </div>
</div> </div>
<pre id="logOutput" tabindex="0"></pre> <pre id="logOutput" tabindex="0"></pre>
+49 -48
View File
@@ -1,21 +1,21 @@
:root { :root {
color-scheme: light; color-scheme: dark;
--bg: #f6f7f4; --bg: #0b0d10;
--surface: #ffffff; --surface: #14181d;
--surface-2: #eeefe9; --surface-2: #1b2027;
--ink: #1f2420; --ink: #edf2f7;
--muted: #697069; --muted: #9aa6b2;
--border: #d7dbd2; --border: #2a313a;
--green: #247857; --green: #38c172;
--green-dark: #155f42; --green-dark: #63d994;
--green-soft: #dff3e9; --green-soft: rgba(56, 193, 114, 0.16);
--amber: #b87516; --amber: #f2b84b;
--amber-soft: #fff1d6; --amber-soft: rgba(242, 184, 75, 0.16);
--red: #b54343; --red: #ff6b6b;
--red-soft: #ffe4e4; --red-soft: rgba(255, 107, 107, 0.15);
--blue: #2d6d9d; --blue: #58a6ff;
--blue-soft: #deeff8; --blue-soft: rgba(88, 166, 255, 0.15);
--shadow: 0 18px 50px rgba(31, 36, 32, 0.1); --shadow: 0 18px 50px rgba(0, 0, 0, 0.38);
--radius: 8px; --radius: 8px;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
} }
@@ -69,9 +69,9 @@ svg {
top: 0; top: 0;
height: 100vh; height: 100vh;
padding: 20px 16px; padding: 20px 16px;
background: #1e241f; background: #0f1216;
color: #f7f8f4; color: var(--ink);
border-right: 1px solid #111611; border-right: 1px solid #222932;
} }
.brand { .brand {
@@ -87,8 +87,8 @@ svg {
place-items: center; place-items: center;
width: 42px; width: 42px;
height: 42px; height: 42px;
border: 1px solid rgba(255, 255, 255, 0.18); border: 1px solid rgba(255, 255, 255, 0.16);
background: #2b6f50; background: #247857;
border-radius: 8px; border-radius: 8px;
font-weight: 800; font-weight: 800;
} }
@@ -101,7 +101,7 @@ svg {
.brand span { .brand span {
display: block; display: block;
margin-top: 2px; margin-top: 2px;
color: #b8c2ba; color: var(--muted);
font-size: 12px; font-size: 12px;
} }
@@ -116,14 +116,14 @@ svg {
gap: 10px; gap: 10px;
min-height: 40px; min-height: 40px;
padding: 0 10px; padding: 0 10px;
color: #cbd3cc; color: #b6c0ca;
text-decoration: none; text-decoration: none;
border-radius: 8px; border-radius: 8px;
} }
.nav-link:hover, .nav-link:hover,
.nav-link.is-active { .nav-link.is-active {
background: rgba(255, 255, 255, 0.09); background: rgba(255, 255, 255, 0.08);
color: #ffffff; color: #ffffff;
} }
@@ -230,7 +230,7 @@ h2 {
border: 1px solid var(--border); border: 1px solid var(--border);
background: var(--surface); background: var(--surface);
border-radius: var(--radius); border-radius: var(--radius);
box-shadow: 0 1px 0 rgba(31, 36, 32, 0.04); box-shadow: 0 1px 0 rgba(255, 255, 255, 0.03);
} }
.metric span, .metric span,
@@ -298,7 +298,7 @@ h2 {
.segmented button.is-selected { .segmented button.is-selected {
background: var(--surface); background: var(--surface);
color: var(--ink); color: var(--ink);
box-shadow: 0 1px 2px rgba(31, 36, 32, 0.08); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.28);
} }
.game-grid { .game-grid {
@@ -315,7 +315,7 @@ h2 {
border: 1px solid var(--border); border: 1px solid var(--border);
background: var(--surface); background: var(--surface);
border-radius: var(--radius); border-radius: var(--radius);
box-shadow: 0 1px 0 rgba(31, 36, 32, 0.04); box-shadow: 0 1px 0 rgba(255, 255, 255, 0.03);
} }
.game-title { .game-title {
@@ -366,31 +366,31 @@ h2 {
} }
.pill.ok { .pill.ok {
border-color: #b7dec9; border-color: rgba(56, 193, 114, 0.34);
background: var(--green-soft); background: var(--green-soft);
color: var(--green-dark); color: var(--green-dark);
} }
.pill.warn { .pill.warn {
border-color: #f0cd91; border-color: rgba(242, 184, 75, 0.38);
background: var(--amber-soft); background: var(--amber-soft);
color: #81500b; color: var(--amber);
} }
.pill.external { .pill.external {
border-color: #b7d8e8; border-color: rgba(88, 166, 255, 0.34);
background: var(--blue-soft); background: var(--blue-soft);
color: #1e547a; color: var(--blue);
} }
.pill.shadow { .pill.shadow {
border-color: #d0c4eb; border-color: rgba(181, 146, 255, 0.34);
background: #eee8fb; background: rgba(181, 146, 255, 0.14);
color: #584080; color: #c7b1ff;
} }
.pill.blocked { .pill.blocked {
border-color: #f0b8b8; border-color: rgba(255, 107, 107, 0.34);
background: var(--red-soft); background: var(--red-soft);
color: var(--red); color: var(--red);
} }
@@ -414,7 +414,7 @@ h2 {
.version-box { .version-box {
min-width: 0; min-width: 0;
padding: 10px; padding: 10px;
background: #f8f9f6; background: var(--surface-2);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
} }
@@ -497,7 +497,7 @@ h2 {
gap: 12px; gap: 12px;
align-items: center; align-items: center;
padding: 12px; padding: 12px;
background: #f8f9f6; background: var(--surface-2);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
} }
@@ -521,7 +521,7 @@ h2 {
height: 8px; height: 8px;
margin-top: 10px; margin-top: 10px;
overflow: hidden; overflow: hidden;
background: #e3e6de; background: #242b33;
border-radius: 999px; border-radius: 999px;
} }
@@ -555,7 +555,7 @@ h2 {
min-height: 38px; min-height: 38px;
padding: 0 10px; padding: 0 10px;
border: 1px solid var(--border); border: 1px solid var(--border);
background: #f8f9f6; background: var(--surface-2);
color: var(--ink); color: var(--ink);
border-radius: 8px; border-radius: 8px;
} }
@@ -566,7 +566,7 @@ h2 {
gap: 10px; gap: 10px;
padding: 10px; padding: 10px;
border: 1px solid var(--border); border: 1px solid var(--border);
background: #f8f9f6; background: var(--surface-2);
border-radius: 8px; border-radius: 8px;
} }
@@ -622,7 +622,7 @@ h2 {
gap: 8px; gap: 8px;
align-items: center; align-items: center;
padding: 8px; padding: 8px;
background: #f8f9f6; background: var(--surface-2);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
} }
@@ -646,7 +646,7 @@ h2 {
min-height: 26px; min-height: 26px;
padding: 0 8px; padding: 0 8px;
background: var(--blue-soft); background: var(--blue-soft);
color: #1e547a; color: var(--blue);
border-radius: 999px; border-radius: 999px;
font-size: 12px; font-size: 12px;
font-weight: 800; font-weight: 800;
@@ -662,8 +662,8 @@ pre {
margin: 0; margin: 0;
padding: 12px; padding: 12px;
overflow: auto; overflow: auto;
background: #1f2420; background: #0f1216;
color: #e9eee8; color: #dfe7ef;
border-radius: 8px; border-radius: 8px;
font-size: 12px; font-size: 12px;
line-height: 1.55; line-height: 1.55;
@@ -685,8 +685,9 @@ pre {
z-index: 10; z-index: 10;
max-width: min(420px, calc(100vw - 36px)); max-width: min(420px, calc(100vw - 36px));
padding: 12px 14px; padding: 12px 14px;
color: #ffffff; color: var(--ink);
background: #1f2420; background: #171c22;
border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
box-shadow: var(--shadow); box-shadow: var(--shadow);
opacity: 0; opacity: 0;