diff --git a/icon-512.png b/icon-512.png new file mode 100644 index 0000000..817c09d Binary files /dev/null and b/icon-512.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..fefd266 --- /dev/null +++ b/index.html @@ -0,0 +1,1247 @@ + + + + + + + + + + + + +Business Trip Day Tracker + + + + +
Saved
+ + +
+
Business Trip Day Tracker
+
Set up your trip to begin
+
+ +
+
+ + +
+ + + +
+ + + + diff --git a/manifest.webmanifest b/manifest.webmanifest new file mode 100644 index 0000000..59cb231 --- /dev/null +++ b/manifest.webmanifest @@ -0,0 +1,15 @@ +{ + "name": "Business Trip Day Tracker", + "short_name": "Trip Log", + "description": "Offline day-by-day record of a business trip for tax substantiation.", + "start_url": "./index.html", + "scope": "./", + "display": "standalone", + "orientation": "portrait", + "background_color": "#0b1220", + "theme_color": "#0b1220", + "icons": [ + { "src": "icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }, + { "src": "icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } + ] +} diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..5cfaf16 --- /dev/null +++ b/sw.js @@ -0,0 +1,53 @@ +/* Business Trip Day Tracker — offline shell. + Cache-first on purpose: this app is a static document whose whole point is + working in airplane mode, so a stale-by-a-day shell beats a failed load. + Bump CACHE when index.html changes; activate() drops every older cache. */ +var CACHE = "trip-tracker-v3"; +var ASSETS = ["./", "./index.html", "./manifest.webmanifest", "./icon-512.png"]; + +self.addEventListener("install", function(e){ + e.waitUntil( + caches.open(CACHE).then(function(c){ + /* addAll is all-or-nothing; one 404 would leave us with no offline copy + at all, so fetch them individually and keep whatever succeeds. */ + return Promise.all(ASSETS.map(function(u){ + return fetch(u, {cache:"reload"}) + .then(function(r){ if(r && r.ok) return c.put(u, r); }) + .catch(function(){}); + })); + }).then(function(){ return self.skipWaiting(); }) + ); +}); + +self.addEventListener("activate", function(e){ + e.waitUntil( + caches.keys().then(function(keys){ + return Promise.all(keys.map(function(k){ + return k === CACHE ? null : caches.delete(k); + })); + }).then(function(){ return self.clients.claim(); }) + ); +}); + +self.addEventListener("fetch", function(e){ + var req = e.request; + if(req.method !== "GET") return; + if(new URL(req.url).origin !== self.location.origin) return; + + e.respondWith( + caches.match(req, {ignoreSearch:true}).then(function(hit){ + if(hit) return hit; + return fetch(req).then(function(res){ + if(res && res.ok){ + var copy = res.clone(); + caches.open(CACHE).then(function(c){ c.put(req, copy); }); + } + return res; + }).catch(function(){ + /* Offline and not cached — a navigation still gets the app shell. */ + if(req.mode === "navigate") return caches.match("./index.html"); + throw new Error("offline"); + }); + }) + ); +});