diff --git a/index.html b/index.html index cc9f986..1b28d28 100644 --- a/index.html +++ b/index.html @@ -4,8 +4,15 @@ - + + + + + + + + diff --git a/public/app-192.png b/public/app-192.png new file mode 100644 index 0000000..c0b1200 Binary files /dev/null and b/public/app-192.png differ diff --git a/public/app-512.png b/public/app-512.png new file mode 100644 index 0000000..678e4ef Binary files /dev/null and b/public/app-512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..52b299e Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/favicon.svg b/public/favicon.svg index aa5db24..45f8fb9 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,7 +1,16 @@ - - - - - - + + + + + + + + + + + + + + + diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..9d3ba37 --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,23 @@ +{ + "name": "amerc — agent mercenary tavern", + "short_name": "amerc", + "description": "Hire, run, publish, and deliver AI agents across software boundaries. No infrastructure, just skills.", + "id": "/", + "start_url": "/?source=pwa", + "scope": "/", + "display": "standalone", + "orientation": "any", + "background_color": "#0e0a14", + "theme_color": "#0e0a14", + "categories": ["productivity", "developer", "business"], + "icons": [ + { "src": "/app-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" }, + { "src": "/app-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }, + { "src": "/app-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } + ], + "shortcuts": [ + { "name": "Browse Agents", "short_name": "Browse", "url": "/#/agents" }, + { "name": "My Booth", "short_name": "Booth", "url": "/#/booth" }, + { "name": "The Mansion", "short_name": "Mansion", "url": "/#/mansion" } + ] +} diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..6a1d40c --- /dev/null +++ b/public/sw.js @@ -0,0 +1,43 @@ +/* amerc service worker — offline app shell + asset caching. + Strategy: navigations = network-first (deploys never go stale), hashed + assets = stale-while-revalidate, /api = always bypass (auth + dynamic). */ +const CACHE = 'amerc-v1'; +const SHELL = '/index.html'; + +self.addEventListener('install', (e) => { + e.waitUntil(caches.open(CACHE).then((c) => c.addAll([SHELL, '/favicon.svg', '/app-192.png'])).catch(() => {})); + self.skipWaiting(); +}); + +self.addEventListener('activate', (e) => { + e.waitUntil( + caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))).then(() => self.clients.claim()) + ); +}); + +self.addEventListener('fetch', (e) => { + const req = e.request; + const url = new URL(req.url); + // Only handle same-origin GET; never touch the API or cross-origin (relays, iframes). + if (req.method !== 'GET' || url.origin !== self.location.origin || url.pathname.startsWith('/api')) return; + + // Navigations: network-first, fall back to cached shell when offline. + if (req.mode === 'navigate') { + e.respondWith( + fetch(req).then((res) => { const copy = res.clone(); caches.open(CACHE).then((c) => c.put(SHELL, copy)); return res; }) + .catch(() => caches.match(SHELL).then((m) => m || caches.match(req))) + ); + return; + } + + // Static assets: serve from cache fast, refresh in the background. + e.respondWith( + caches.match(req).then((cached) => { + const network = fetch(req).then((res) => { + if (res && res.status === 200 && res.type === 'basic') { const copy = res.clone(); caches.open(CACHE).then((c) => c.put(req, copy)); } + return res; + }).catch(() => cached); + return cached || network; + }) + ); +}); diff --git a/src/Scene2D.jsx b/src/Scene2D.jsx index b36a50c..7dd9aba 100644 --- a/src/Scene2D.jsx +++ b/src/Scene2D.jsx @@ -7,7 +7,7 @@ import Account from './Account.jsx'; import TavernGame from './TavernGame.jsx'; import { api } from './api.js'; -const RELEASE = '0.39.0-docs'; +const RELEASE = '0.40.0-pwa'; const NAV = [ { id: 'home', label: 'Tavern' }, diff --git a/src/main.jsx b/src/main.jsx index db19764..2b1bd53 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -19,3 +19,9 @@ createRoot(document.getElementById('root')).render( , ); + +// Progressive Web App: register the service worker on production amerc.ai hosts only +// (skip the Vite dev server so HMR keeps working). +if ('serviceWorker' in navigator && host.endsWith('amerc.ai')) { + window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js').catch(() => {}); }); +}