Files
dogfather-universe/webdesign/sw.js
T
DogFatherGitandClaude Opus 5 76aea6ac3f Bilder bewegen sich unter dem Zeiger
Faehrt der Zeiger ueber ein Bild, tritt es leicht naeher und wandert ein
Stueck GEGEN die Zeigerrichtung -- als schaue man durch ein Fenster und
lehne sich zur Seite.

Warum gegen die Richtung: Bewegt sich das Bild MIT dem Zeiger, wirkt es
wie ein Aufkleber, der verrutscht. Nur die Gegenbewegung liest das Auge
als Tiefe hinter dem Rahmen. Derselbe Grund wie bei der Buehne im
Verwaltungsbereich, eine Ebene kleiner.

Der Effekt liegt im GRUNDSYSTEM, nicht in einer einzelnen Seite. Er gilt
damit ueberall: Portfolio, Startseite, "Ueber mich", Zugangswand und die
Vorschaubilder im Verwaltungsbereich. Eine Sonderloesung je Seite waere
beim naechsten neuen Bild wieder vergessen worden.

Zwei Zahlen, die bewusst klein sind:

- Der Ausschlag liegt bei hoechstens 14 Punkten (gemessen 13,9).
- Der Zoom bei 1,055.

Zusammen ergibt das Bewegung, ohne dass ein Bild beim blossen
Vorbeifahren seinen Ausschnitt merklich aendert. Ein groesserer Wert
waere kein Effekt mehr, sondern ein Bildsprung.

Umgerechnet wird auf die Groesse des jeweiligen Bildes (-1 bis +1), nicht
in festen Bildpunkten. Sonst wanderten ein Vorschaubild von 1200 Punkten
Breite und ein Logo von 80 gleich weit -- beim kleinen saehe das aus wie
ein Ruck.

Zwei Faelle, die im Code ausdruecklich abgefangen sind:

- Der Zeiger liegt ueber einem Element, das das Bild UEBERDECKT (etwa
  einem Textblock in derselben Kachel). Ohne Behandlung bliebe das Bild
  stehen, sobald man den Rahmen verlaesst, aber die Kachel noch nicht.
- Der Zeiger steht neben dem Bild, aber noch in der Kachel. Die Werte
  liefen dann weit ueber 1 hinaus und das Bild schoesse aus dem Rahmen.
  Deshalb wird auf -1 bis +1 begrenzt.

Beim Verlassen stellt sich alles zurueck -- sonst bliebe das Bild
verschoben stehen, nachdem der Zeiger laengst weg ist.

Bei prefers-reduced-motion ist der Effekt aus.

Geprueft: 265 Pruefungen gruen (Portfolio 20, Verwaltung 62, CRYONOVA 53,
Blickfang 13, System 5, Abmelden 16, Abbruch 42, Angebot 54). Der Test
prueft ausdruecklich die RICHTUNG der Bewegung, nicht nur, dass sich
ueberhaupt etwas tut.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 09:48:17 +02:00

192 lines
7.9 KiB
JavaScript

/* =====================================================================
sw.js — Service Worker NUR für /webdesign/
Macht den Bereich auf Handy und Computer als App installierbar und
sorgt dafür, dass die Oberfläche auch bei wackeliger Verbindung
sofort da ist.
⚠️ SICHERHEITSENTSCHEIDUNG, DIE HIER DEN AUSSCHLAG GIBT
Der Webdesign-Bereich ist zugangsgeschützt (server/webdesign-gate.js).
Ein Service Worker, der HTML-Seiten zwischenspeichert, würde diesen
Schutz aushebeln: Nach dem Abmelden — oder nachdem jemand anders das
Gerät in die Hand nimmt — lieferte der Zwischenspeicher die geschützten
Seiten samt Preisen, Anfragen und Kundendaten weiterhin aus, ohne dass
der Server je gefragt wird. Genau das ist der klassische Fehler bei
PWAs hinter einer Anmeldung.
Deshalb gilt hier strikt:
- HTML-Seiten werden NIEMALS zwischengespeichert. Immer Netz.
Ist kein Netz da, kommt eine schlichte Offline-Meldung — nicht die
zuletzt gesehene, möglicherweise vertrauliche Seite.
- Zwischengespeichert werden ausschließlich die neutralen Bausteine
unter /assets/ (Stilvorlagen, Skripte, Logo, Icons). Die enthalten
keine Kunden- oder Projektdaten und liegen ohnehin außerhalb der
Zugangsschranke.
- Antworten der API (postfach.dogfather-universe.com) werden
ebenfalls nie zwischengespeichert.
Das kostet Offline-Komfort. Der ist hier aber weniger wert als die
Zusicherung, dass nach dem Schließen der Sitzung wirklich nichts mehr
einsehbar ist.
===================================================================== */
/* Version im Namen: eine Änderung dieser Zeichenkette wirft beim nächsten
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
Stand fest. */
const CACHE_NAME = "dogfather-webdesign-v43";
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
HTML-Datei in dieser Liste. */
const GRUNDGERUEST = [
"/assets/css/main.css",
"/assets/css/webdesign.css",
"/assets/js/wd-core.js",
"/assets/img/logo-dogfather-transparent.png",
"/assets/img/wd-app-192.png",
"/assets/img/wd-app-512.png",
];
self.addEventListener("install", (ereignis) => {
ereignis.waitUntil(
caches.open(CACHE_NAME).then((speicher) =>
/* addAll bricht komplett ab, wenn EINE Datei fehlt — dann wäre der
Service Worker gar nicht erst installiert und die App nicht
installierbar. Deshalb jede Datei einzeln und Fehler schlucken. */
Promise.all(
GRUNDGERUEST.map((pfad) =>
speicher.add(new Request(pfad, { cache: "reload" })).catch(() => {})
)
)
)
);
/* Sofort übernehmen statt auf das Schließen aller Tabs zu warten —
sonst bleibt nach einem Deploy tagelang die alte Fassung aktiv. */
self.skipWaiting();
});
self.addEventListener("activate", (ereignis) => {
ereignis.waitUntil(
caches.keys()
.then((namen) => Promise.all(
namen.filter((n) => n !== CACHE_NAME).map((n) => caches.delete(n))
))
.then(() => self.clients.claim())
);
});
/* Schlichte Offline-Meldung. Bewusst als erzeugte Antwort statt als
Datei — so kann sie nie mit echtem Seiteninhalt verwechselt werden.
Fünf Sprachen wären hier Überinstrumentierung: der Text erscheint nur
ohne Internet und ist absichtlich sehr kurz gehalten. */
function offlineSeite() {
return new Response(
`<!DOCTYPE html><html lang="de"><head><meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Keine Verbindung</title>
<style>
body{margin:0;min-height:100svh;display:flex;align-items:center;justify-content:center;
background:#05070b;color:#eef4fb;font-family:system-ui,sans-serif;text-align:center;padding:2rem}
.k{max-width:34ch}
h1{font-size:1.4rem;margin:0 0 .6rem}
p{color:#b3c2d4;line-height:1.6;margin:0 0 1.4rem}
button{min-height:48px;padding:.8rem 1.6rem;border:none;border-radius:999px;
background:#7fd0e8;color:#04070c;font-weight:700;font-size:1rem;cursor:pointer}
</style></head><body>
<div class="k">
<h1>Keine Verbindung</h1>
<p>Dogfather Webdesign braucht eine Internetverbindung. Sobald du wieder online bist, geht es normal weiter.</p>
<button onclick="location.reload()">Erneut versuchen</button>
</div></body></html>`,
{ status: 503, headers: { "Content-Type": "text/html; charset=utf-8" } }
);
}
self.addEventListener("fetch", (ereignis) => {
const anfrage = ereignis.request;
// Nur einfache Abrufe. Absendungen (POST) gehen immer direkt ans Netz.
if (anfrage.method !== "GET") return;
const url = new URL(anfrage.url);
// Fremde Server (API, externe Bilder) nie anfassen.
if (url.origin !== self.location.origin) return;
/* --- HTML: immer Netz, niemals zwischenspeichern (siehe Kopf) --- */
const willHtml = anfrage.mode === "navigate" ||
(anfrage.headers.get("accept") || "").includes("text/html");
if (willHtml) {
ereignis.respondWith(
fetch(anfrage).catch(() => offlineSeite())
);
return;
}
/* --- Zugangs-Endpunkte niemals zwischenspeichern --- */
if (url.pathname.startsWith("/webdesign/zugang-")) return;
/* --- Neutrale Bausteine unter /assets/ ---
⚠️ HIER WAR EIN ECHTER FEHLER (gemeldet 22.08.2026: "immer noch so").
Vorher lief das als "stale-while-revalidate": die gespeicherte Fassung
ging sofort raus, im Hintergrund wurde eine frische geholt. Das ist
schnell — bedeutet aber, dass jede Korrektur an CSS oder JavaScript
beim ersten Aufruf UNSICHTBAR bleibt und erst beim zweiten wirkt.
Genau das ist passiert: die Korrektur am gestreckten Logo war auf dem
Server längst live, der Browser hat aber die alte Stilvorlage aus
meinem eigenen Zwischenspeicher angezeigt. Ein Fehler, der wie ein
nicht behobener Fehler aussieht — die unangenehmste Sorte.
Jetzt: erst Netz, Zwischenspeicher nur als Notfallnetz.
Für Stilvorlagen und Skripte ist Richtigkeit wichtiger als die
eingesparten Millisekunden — eine falsche Darstellung kostet mehr
als ein kurzer Ladevorgang. Bilder und Symbole bleiben beim schnellen
Weg: die ändern sich praktisch nie, und wenn doch, bekommen sie einen
neuen Dateinamen. */
if (url.pathname.startsWith("/assets/")) {
const istCodeOderStil = /\.(css|js|mjs)$/i.test(url.pathname);
if (istCodeOderStil) {
// Netz zuerst
ereignis.respondWith(
fetch(anfrage)
.then((antwort) => {
if (antwort && antwort.status === 200 && antwort.type === "basic") {
const kopie = antwort.clone();
caches.open(CACHE_NAME).then((speicher) => speicher.put(anfrage, kopie));
}
return antwort;
})
.catch(() => caches.match(anfrage).then((g) => g || Response.error()))
);
return;
}
// Bilder, Symbole, Schriften: Zwischenspeicher zuerst
ereignis.respondWith(
caches.open(CACHE_NAME).then(async (speicher) => {
const gespeichert = await speicher.match(anfrage);
const ausDemNetz = fetch(anfrage)
.then((antwort) => {
/* Nur vollständige, eigene Antworten ablegen. Teilantworten
(206, z.B. bei Video-Sprüngen) lassen sich nicht sinnvoll
zwischenspeichern und würden den Speicher beschädigen. */
if (antwort && antwort.status === 200 && antwort.type === "basic") {
speicher.put(anfrage, antwort.clone());
}
return antwort;
})
.catch(() => null);
return gespeichert || (await ausDemNetz) || Response.error();
})
);
return;
}
/* Alles Übrige: kein respondWith -> der Browser macht es ganz normal
selbst. Das ist die sicherste Voreinstellung. */
});