diff --git a/src/app.html b/src/app.html
index 355b414..cc5f934 100644
--- a/src/app.html
+++ b/src/app.html
@@ -4,6 +4,20 @@
+
+
+
+
+
+
+
+
%sveltekit.head%
diff --git a/src/service-worker.js b/src/service-worker.js
new file mode 100644
index 0000000..0976a0c
--- /dev/null
+++ b/src/service-worker.js
@@ -0,0 +1,80 @@
+///
+import { build, files, version } from '$service-worker';
+
+// Create a unique cache name for this deployment
+const CACHE = `cache-${version}`;
+
+const ASSETS = [
+ ...build, // the app itself
+ ...files // everything in `static`
+];
+
+self.addEventListener('install', (event) => {
+ // Create a new cache and add all files to it
+ async function addFilesToCache() {
+ const cache = await caches.open(CACHE);
+ await cache.addAll(ASSETS);
+ }
+
+ event.waitUntil(addFilesToCache());
+});
+
+self.addEventListener('activate', (event) => {
+ // Remove previous cached data from disk
+ async function deleteOldCaches() {
+ for (const key of await caches.keys()) {
+ if (key !== CACHE) await caches.delete(key);
+ }
+ }
+
+ event.waitUntil(deleteOldCaches());
+});
+
+self.addEventListener('fetch', (event) => {
+ // ignore POST requests etc
+ if (event.request.method !== 'GET') return;
+
+ async function respond() {
+ const url = new URL(event.request.url);
+ const cache = await caches.open(CACHE);
+
+ // `build`/`files` can always be served from the cache
+ if (ASSETS.includes(url.pathname)) {
+ const response = await cache.match(url.pathname);
+
+ if (response) {
+ return response;
+ }
+ }
+
+ // for everything else, try the network first, but
+ // fall back to the cache if we're offline
+ try {
+ const response = await fetch(event.request);
+
+ // if we're offline, fetch can return a value that is not a Response
+ // instead of throwing - and we can't pass this non-Response to respondWith
+ if (!(response instanceof Response)) {
+ throw new Error('invalid response from fetch');
+ }
+
+ if (response.status === 200) {
+ cache.put(event.request, response.clone());
+ }
+
+ return response;
+ } catch (err) {
+ const response = await cache.match(event.request);
+
+ if (response) {
+ return response;
+ }
+
+ // if there's no cache, then just error out
+ // as there is nothing we can do to respond to this request
+ throw err;
+ }
+ }
+
+ event.respondWith(respond());
+});
diff --git a/static/OIG3.jpeg b/static/OIG3.jpeg
new file mode 100644
index 0000000..278abda
Binary files /dev/null and b/static/OIG3.jpeg differ
diff --git a/static/android-chrome-192x192.png b/static/android-chrome-192x192.png
new file mode 100644
index 0000000..ed6b7e3
Binary files /dev/null and b/static/android-chrome-192x192.png differ
diff --git a/static/android-chrome-512x512.png b/static/android-chrome-512x512.png
new file mode 100644
index 0000000..440544d
Binary files /dev/null and b/static/android-chrome-512x512.png differ
diff --git a/static/site.webmanifest b/static/site.webmanifest
new file mode 100644
index 0000000..f6d47b6
--- /dev/null
+++ b/static/site.webmanifest
@@ -0,0 +1,22 @@
+{
+ "name": "Living Dex Tracker",
+ "short_name": "Living Dex Tracker",
+ "description": "A tool to enable you to track your progress in completing the living Pokédex in Pokémon games.",
+ "icons": [
+ {
+ "src": "/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any maskable"
+ }
+ ],
+ "start_url": "/",
+ "theme_color": "#f00000",
+ "background_color": "#f0f0f0",
+ "display": "standalone"
+}