Installable as an app (PWA): manifests, offline-only service worker, icons

This commit is contained in:
Astra 2026-10-03 12:22:33 +01:00
parent b26360e8f3
commit 44708d5f3c
16 changed files with 246 additions and 0 deletions

View file

@ -3,6 +3,10 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#1b1e25">
<link rel="manifest" href="/admin.webmanifest">
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/apple-touch-icon.png">
<title>Staff panel</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -165,3 +165,6 @@ function toast(msg) {
}
const steamProfile = (sid) => `https://steamcommunity.com/profiles/${encodeURIComponent(sid)}`;
// The service worker makes the site installable as an app (see sw.js).
if ("serviceWorker" in navigator) navigator.serviceWorker.register("/sw.js").catch(() => {});

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<rect width="512" height="512" fill="#14161b"/>
<g transform="translate(112 112) scale(9)">
<path d="M16 2 28 7v9c0 7-5 12-12 14C9 28 4 23 4 16V7z" fill="none" stroke="#fe113d" stroke-width="2.5"/>
<path d="M11 16h10M16 11v10" stroke="#e6e8ee" stroke-width="2.5" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 377 B

View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<rect width="512" height="512" rx="112" fill="#14161b"/>
<g transform="translate(64 64) scale(12)">
<path d="M16 2 28 7v9c0 7-5 12-12 14C9 28 4 23 4 16V7z" fill="none" stroke="#fe113d" stroke-width="2.5"/>
<path d="M11 16h10M16 11v10" stroke="#e6e8ee" stroke-width="2.5" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 385 B

View file

@ -0,0 +1,55 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#1b1e25">
<!-- Served in place of whatever page failed to load, so a refresh retries that page. -->
<meta http-equiv="refresh" content="15">
<title>Offline</title>
<!-- Self-contained: nothing else is cached, so styles.css and the fonts can't load from here. -->
<style>
html, body { margin: 0; height: 100%; }
body {
display: grid;
place-items: center;
padding: 16px;
background: #14161b;
color: #e6e8ee;
font: 16px/1.5 system-ui, sans-serif;
color-scheme: dark;
}
main {
max-width: 360px;
padding: 32px 24px;
text-align: center;
background: #1b1e25;
border: 1px solid #2e333f;
border-radius: 12px;
}
svg { width: 48px; height: 48px; }
h1 { margin: 12px 0 4px; font-size: 22px; }
p { margin: 0 0 20px; color: #8a91a0; }
a {
display: inline-block;
padding: 8px 18px;
background: #fe113d;
color: #fff;
border-radius: 8px;
font-weight: 600;
text-decoration: none;
}
</style>
</head>
<body>
<main>
<svg viewBox="0 0 32 32" aria-hidden="true">
<path d="M16 2 28 7v9c0 7-5 12-12 14C9 28 4 23 4 16V7z" fill="none" stroke="#fe113d" stroke-width="2.5"/>
<path d="M11 16h10M16 11v10" stroke="#e6e8ee" stroke-width="2.5" stroke-linecap="round"/>
</svg>
<h1>Can't reach the panel</h1>
<p>Check your connection. This page tries again by itself every few seconds.</p>
<a href="">Try again</a>
</main>
</body>
</html>

View file

@ -3,6 +3,10 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#1b1e25">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/apple-touch-icon.png">
<title>Bans and staff</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

View file

@ -3,6 +3,10 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#1b1e25">
<link rel="manifest" href="/admin.webmanifest">
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/apple-touch-icon.png">
<title>Staff sign in</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

23
internal/web/assets/sw.js Normal file
View file

@ -0,0 +1,23 @@
// The service worker is what lets the site be installed as an app. It caches nothing but the
// offline page: every request still goes to the network, so nobody sees stale bans or ranks.
const CACHE = "offline-v1";
const OFFLINE = "/static/offline.html";
self.addEventListener("install", (e) => {
e.waitUntil(caches.open(CACHE).then((c) => c.add(OFFLINE)).then(() => self.skipWaiting()));
});
self.addEventListener("activate", (e) => {
e.waitUntil(
caches.keys()
.then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
.then(() => self.clients.claim()),
);
});
// Only page loads get the offline page; API calls fail as they always have, and the pages say so.
self.addEventListener("fetch", (e) => {
if (e.request.mode !== "navigate") return;
e.respondWith(fetch(e.request).catch(() => caches.match(OFFLINE)));
});