Als App auf dem PC installierbar — mit gut sichtbarem Installations-Button

Die Icons/Manifeste waren schon vorbereitet, aber es gab keinen echten
Weg, die Installation auch zu STARTEN, ohne das kleine, leicht zu
übersehende Icon im Browser selbst zu suchen. Jetzt:

- Ein Service Worker (public/sw.js) ist Voraussetzung dafür, dass
  Browser die Seite überhaupt als "installierbare App" erkennen. Bewusst
  minimal gehalten: strikt "network-first" — bei bestehender Verbindung
  wird IMMER zuerst das echte Netzwerk gefragt, der Cache dient nur als
  Rückfalllösung, wenn wirklich keine Verbindung besteht. Dadurch besteht
  KEIN Risiko, dass der Adminbereich veraltete Inhalte zeigt, während man
  online ist — als Bonus funktionieren bereits besuchte Seiten aber auch
  kurz offline (mit echtem Offline-Test geprüft).
- Neuer "Installieren"-Button direkt in der Kopfzeile (Hauptseite) bzw.
  unten rechts (Adminbereich) — erscheint automatisch, sobald der
  Browser eine Installation wirklich anbietet, verschwindet danach
  wieder bzw. bleibt komplett versteckt, wenn schon als App installiert.
  Ein Klick reicht, kein Suchen im Browser-Menü mehr nötig.
- Beide Bereiche (Hauptseite + Admin) nutzen dieselben, bereits
  vorhandenen Logo-Icons/Manifeste — landet also mit dem echten
  VanVan-Logo auf dem Startbildschirm/Desktop.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
qcigano
2026-08-03 00:16:23 +02:00
co-authored by Claude Sonnet 5
parent 0ff6412e1e
commit 4aee6c7b98
4 changed files with 169 additions and 0 deletions
+56
View File
@@ -65,6 +65,30 @@
h1, h2, h3 {
font-family: "Fraunces", "Playfair Display", Georgia, serif !important;
}
/* Schwebender "Installieren"-Button — nur sichtbar, wenn der Browser eine Installation
wirklich anbietet (siehe Script unten). position:fixed statt normaler Dokumentfluss,
damit er unabhängig davon funktioniert, wie Sveltia sein eigenes UI in <body> aufbaut. */
#install-btn {
display: none;
position: fixed;
bottom: 1.2rem;
right: 1.2rem;
z-index: 9999;
align-items: center;
gap: 0.5em;
padding: 0.7em 1.1em;
border-radius: 999px;
background: linear-gradient(135deg, #9b7fd6, #2e1b47);
border: 1px solid rgba(155, 127, 214, 0.6);
color: #f4f1f7;
font-family: "Inter", system-ui, sans-serif;
font-size: 0.88rem;
font-weight: 600;
cursor: pointer;
box-shadow: 0 10px 24px -8px rgba(0, 0, 0, 0.55);
}
#install-btn:hover { filter: brightness(1.1); }
</style>
</head>
<body>
@@ -72,5 +96,37 @@
direkt im GitHub-Repo. Kein eigener Server nötig — siehe config.yml für die
Feld-Definitionen (Produkte, Kategorien). -->
<script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script>
<button type="button" id="install-btn" title="Admin als App installieren">⬇️ Installieren</button>
<script>
// Gleiches Prinzip wie auf der Hauptseite: Button nur zeigen, wenn der Browser die
// Installation wirklich anbietet, und nicht mehr, sobald bereits als App installiert.
(function () {
var btn = document.getElementById("install-btn");
var laeuftBereitsAlsApp = window.matchMedia("(display-mode: standalone)").matches;
var promptEvent = null;
if (!laeuftBereitsAlsApp) {
window.addEventListener("beforeinstallprompt", function (e) {
e.preventDefault();
promptEvent = e;
btn.style.display = "flex";
});
}
btn.addEventListener("click", function () {
if (!promptEvent) return;
btn.style.display = "none";
promptEvent.prompt();
promptEvent.userChoice.then(function () { promptEvent = null; });
});
window.addEventListener("appinstalled", function () {
btn.style.display = "none";
});
if ("serviceWorker" in navigator) {
window.addEventListener("load", function () {
navigator.serviceWorker.register("/sw.js").catch(function () {});
});
}
})();
</script>
</body>
</html>
+41
View File
@@ -0,0 +1,41 @@
// Minimaler, bewusst zurückhaltender Service Worker — NICHT für "volles Offline", sondern
// hauptsächlich damit der Browser die Seite überhaupt als installierbare App erkennt (die
// meisten Browser verlangen dafür einen registrierten Service Worker mit echtem
// fetch-Handler) und als kleiner Bonus: falls doch mal keine Verbindung besteht, können bereits
// besuchte Seiten trotzdem noch geöffnet werden.
//
// WICHTIG: strikt "network-first" — bei bestehender Verbindung wird IMMER zuerst das echte
// Netzwerk gefragt, der Cache dient nur als Rückfalllösung für den Offline-Fall. Das ist bewusst
// so gewählt, damit z.B. der Adminbereich (Sveltia CMS) NIE veraltete Inhalte aus dem Cache
// zeigt, solange eine Internetverbindung besteht.
const CACHE_NAME = "vandiy-shell-v1";
self.addEventListener("install", () => {
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))
).then(() => self.clients.claim())
);
});
self.addEventListener("fetch", (event) => {
if (event.request.method !== "GET") return;
// Cross-Origin-Requests (Google Fonts, PayPal-SDK, GitHub-API des Admin-Bereichs, ...)
// unangetastet lassen — nur die eigene Seite selbst wird für den Offline-Fall vorgehalten.
if (new URL(event.request.url).origin !== self.location.origin) return;
event.respondWith(
fetch(event.request)
.then((response) => {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone)).catch(() => {});
return response;
})
.catch(() => caches.match(event.request).then((cached) => cached || Response.error()))
);
});