thermograph/static/sw.js
Emi Griffith c6b2e55ff2 Replace the brand mark with the grid-cell logo everywhere (#199)
Swaps the stepped-path mark for the "C1 v2" design: an 8x8 grid of graded
day-cells tracing the same climb (blue Low -> green Normal -> a hot loop -> a
stepped ascent to the near-record red cell). It keeps the exact geometry of the
old mark — same 512 viewBox, same 32/448/rx96 rounded badge, same band palette —
so it is a drop-in that needs no alignment changes.

Everywhere the mark appears:
- The inline header SVG in all six lockups (the five standalone frontend pages
  and backend/templates/base.html.j2), kept at width/height 28 with aria-hidden.
  Because the box and viewBox are unchanged, the .logo/.brand alignment CSS
  (the -2px cap-height nudge, the 28->22px compact size, the 10px gap) is
  untouched and the mark stays centred on the "Thermograph" wordmark.
- favicon.svg, regenerated from the new art.
- Every raster: favicon-16/32/48, apple-touch-icon (180), logo-192, logo (512),
  and the two maskable icons. The maskable pair keeps the established recipe —
  the badge scaled into the central safe zone on a full-bleed opaque square — so
  an OS shape mask never clips the art.

Bumps every icon reference to ?v=3 (previously a mix of ?v=2 and unversioned) so
browsers and installed PWAs refetch the new icons.

Claude-Session: https://claude.ai/code/session_013dRZmX9D3JEntfMKWMTWZ8
2026-07-19 23:45:12 +00:00

54 lines
1.9 KiB
JavaScript

// Thermograph service worker — push delivery only.
//
// Deliberately NO fetch/offline caching: the app already has an app-level
// IndexedDB response cache (cache.js), and a caching service worker would fight
// it. This worker exists purely so the app is installable (a registered SW is a
// PWA prerequisite) and can receive Web Push while no tab is open.
//
// Served from {BASE}/sw.js, so its scope is {BASE}/ — it controls the whole app.
self.addEventListener("install", () => {
self.skipWaiting(); // activate this version immediately
});
self.addEventListener("activate", (event) => {
event.waitUntil(self.clients.claim()); // take control of open pages at once
});
// A push arrived. The payload is the JSON we send from push.py:
// { title, body, url, tag }.
self.addEventListener("push", (event) => {
let data = {};
try {
data = event.data ? event.data.json() : {};
} catch (e) {
data = { title: "Thermograph", body: event.data ? event.data.text() : "" };
}
const title = data.title || "Thermograph";
const options = {
body: data.body || "",
icon: "logo-192.png?v=3",
badge: "logo-192.png?v=3",
tag: data.tag || "thermograph",
data: { url: data.url || "./" },
};
event.waitUntil(self.registration.showNotification(title, options));
});
// Tapping a notification: focus an existing app tab if one is open, else open the
// deep link the notification carried.
self.addEventListener("notificationclick", (event) => {
event.notification.close();
const target = (event.notification.data && event.notification.data.url) || "./";
event.waitUntil(
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clients) => {
for (const client of clients) {
if ("focus" in client) {
client.navigate(target).catch(() => {});
return client.focus();
}
}
return self.clients.openWindow(target);
})
);
});