diff --git a/index.html b/index.html index bf1d45f..5b5cf95 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,12 @@
- + + + + + + diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..eb28865 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000..969a7c2 Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-192x192-maskable.png b/public/favicon-192x192-maskable.png new file mode 100644 index 0000000..dfbea72 Binary files /dev/null and b/public/favicon-192x192-maskable.png differ diff --git a/public/favicon-192x192.png b/public/favicon-192x192.png new file mode 100644 index 0000000..3887e80 Binary files /dev/null and b/public/favicon-192x192.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000..06dfb36 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon-512x512-maskable.png b/public/favicon-512x512-maskable.png new file mode 100644 index 0000000..eae2846 Binary files /dev/null and b/public/favicon-512x512-maskable.png differ diff --git a/public/favicon-512x512.png b/public/favicon-512x512.png new file mode 100644 index 0000000..14ec14a Binary files /dev/null and b/public/favicon-512x512.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..2a7075f Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..8e8f1fe --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,53 @@ +{ + "name": "Grimoire", + "short_name": "Grimoire", + "description": "A tiling window manager interface for exploring the Nostr protocol. Each window is a Nostr app (profile viewer, event feed, NIP documentation, etc.). Commands are launched Unix-style via Cmd+K palette.", + "start_url": "/", + "display": "standalone", + "background_color": "#020817", + "theme_color": "#b366ff", + "orientation": "any", + "icons": [ + { + "src": "/favicon-16x16.png", + "sizes": "16x16", + "type": "image/png" + }, + { + "src": "/favicon-32x32.png", + "sizes": "32x32", + "type": "image/png" + }, + { + "src": "/apple-touch-icon.png", + "sizes": "180x180", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/favicon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/favicon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/favicon-192x192-maskable.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/favicon-512x512-maskable.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "categories": ["social", "utilities", "productivity", "nostr"] +} diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..8824bde --- /dev/null +++ b/public/sw.js @@ -0,0 +1,84 @@ +// Grimoire Service Worker - v1.0.0 +const CACHE_NAME = "grimoire-v1"; +const RUNTIME_CACHE = "grimoire-runtime"; + +// Core assets to cache on install +const PRECACHE_URLS = [ + "/", + "/index.html", + "/favicon.ico", + "/favicon-192x192.png", + "/favicon-512x512.png", +]; + +// Install event - precache core assets +self.addEventListener("install", (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll(PRECACHE_URLS); + }), + ); + // Activate immediately + self.skipWaiting(); +}); + +// Activate event - clean up old caches +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames + .filter((name) => name !== CACHE_NAME && name !== RUNTIME_CACHE) + .map((name) => caches.delete(name)), + ); + }), + ); + // Take control immediately + self.clients.claim(); +}); + +// Fetch event - network first, fallback to cache +self.addEventListener("fetch", (event) => { + // Skip non-GET requests + if (event.request.method !== "GET") return; + + // Skip cross-origin requests + if (!event.request.url.startsWith(self.location.origin)) return; + + // Network first strategy for app shell and assets + event.respondWith( + fetch(event.request) + .then((response) => { + // Clone the response before caching + const responseClone = response.clone(); + + // Cache successful responses + if (response.status === 200) { + caches.open(RUNTIME_CACHE).then((cache) => { + cache.put(event.request, responseClone); + }); + } + + return response; + }) + .catch(() => { + // Fallback to cache if network fails + return caches.match(event.request).then((cachedResponse) => { + if (cachedResponse) { + return cachedResponse; + } + + // If no cache, return offline page for navigation requests + if (event.request.mode === "navigate") { + return caches.match("/index.html"); + } + + // For other requests, just fail + return new Response("Offline", { + status: 503, + statusText: "Service Unavailable", + }); + }); + }), + ); +}); diff --git a/src/main.tsx b/src/main.tsx index dd08235..3959323 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -35,3 +35,17 @@ createRoot(document.getElementById("root")!).render( , ); + +// Register service worker for PWA functionality +if ("serviceWorker" in navigator) { + window.addEventListener("load", () => { + navigator.serviceWorker + .register("/sw.js") + .then((registration) => { + console.log("SW registered:", registration); + }) + .catch((error) => { + console.log("SW registration failed:", error); + }); + }); +}