diff --git a/icons/192.svg b/icons/192.svg
new file mode 100644
index 0000000..780ea92
--- /dev/null
+++ b/icons/192.svg
@@ -0,0 +1,22 @@
+
diff --git a/icons/512.svg b/icons/512.svg
new file mode 100644
index 0000000..f71e23c
--- /dev/null
+++ b/icons/512.svg
@@ -0,0 +1,41 @@
+
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..6b0b7b7
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,16 @@
+{
+ "name": "MuneBase Swerve Drive Sim",
+ "start_url": "/",
+ "icons": [
+ {
+ "src": "icons/192.svg",
+ "sizes": "192x192",
+ "type": "image/svg+xml"
+ },
+ {
+ "src": "icons/512.svg",
+ "sizes": "512x512",
+ "type": "image/svg+xml"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/sw.js b/sw.js
new file mode 100644
index 0000000..68dc439
--- /dev/null
+++ b/sw.js
@@ -0,0 +1,33 @@
+const CACHE_NAME = 'v1';
+const ASSETS = [
+ '/',
+ '/index.html',
+ '/script.js',
+ '/styles.css',
+ '/manifest.json',
+ '/icons/192.svg',
+ '/icons/512.svg',
+ '/vendor/lucio/graham-scan.mjs'
+];
+
+self.addEventListener('install', (e) => {
+ e.waitUntil(
+ caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS))
+ );
+ self.skipWaiting();
+});
+
+self.addEventListener('activate', (e) => {
+ e.waitUntil(
+ caches.keys().then(keys =>
+ Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k)))
+ )
+ );
+ self.clients.claim();
+});
+
+self.addEventListener('fetch', (e) => {
+ e.respondWith(
+ caches.match(e.request).then(response => response || fetch(e.request))
+ );
+});