installable PWA + branded app icons (0.40.0)
- web app manifest: name, standalone display, theme, app shortcuts (Browse/Booth/Mansion), maskable + any icons - branded gem app icons (192/512/apple-touch) + refreshed favicon.svg - service worker: network-first navigations (deploys never go stale), stale-while-revalidate for hashed assets, hard /api bypass, offline shell - registered only on production amerc.ai hosts (dev HMR untouched) - nginx: serve .webmanifest as application/manifest+json (all vhosts) - verified live: installable criteria met, SW controls page, 0 console errors
This commit is contained in:
parent
c8f6d0f892
commit
4ad339a94f
@ -4,8 +4,15 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#0e0a14" />
|
<meta name="theme-color" content="#0e0a14" />
|
||||||
<meta name="description" content="amerc is the agent mercenary tavern — hire, run, publish, and remotely deliver AI agents across software boundaries. No infrastructure, just skills." />
|
<meta name="description" content="amerc is the agent mercenary tavern — hire, run, publish, and remotely deliver AI agents across software boundaries. No infrastructure, just skills." />
|
||||||
<meta name="version" content="0.39.0-docs" />
|
<meta name="version" content="0.40.0-pwa" />
|
||||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||||
|
<link rel="icon" href="/app-192.png" type="image/png" sizes="192x192" />
|
||||||
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="amerc" />
|
||||||
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<!-- Open Graph / social share -->
|
<!-- Open Graph / social share -->
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:site_name" content="amerc" />
|
<meta property="og:site_name" content="amerc" />
|
||||||
|
|||||||
BIN
public/app-192.png
Normal file
BIN
public/app-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
public/app-512.png
Normal file
BIN
public/app-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
BIN
public/apple-touch-icon.png
Normal file
BIN
public/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@ -1,7 +1,16 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
||||||
<rect width="64" height="64" rx="10" fill="#170704"/>
|
<defs>
|
||||||
<path d="M12 48h40l-6-30H18z" fill="#8b4a22"/>
|
<radialGradient id="glow" cx="50%" cy="42%" r="55%">
|
||||||
<path d="M20 18h24l4 22H16z" fill="#321207"/>
|
<stop offset="0%" stop-color="#28b4ff" stop-opacity="0.55"/>
|
||||||
<path d="M27 10h10l9 9-14 14-14-14z" fill="#42dcff"/>
|
<stop offset="100%" stop-color="#28b4ff" stop-opacity="0"/>
|
||||||
<path d="M22 47c4-11 16-11 20 0" fill="none" stroke="#f7bc5d" stroke-width="5" stroke-linecap="round"/>
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="64" height="64" rx="14" fill="#160d22"/>
|
||||||
|
<circle cx="32" cy="29" r="22" fill="url(#glow)"/>
|
||||||
|
<polygon points="32,12 32,32 14,32" fill="#5adcff"/>
|
||||||
|
<polygon points="32,12 50,32 32,32" fill="#3cbef0"/>
|
||||||
|
<polygon points="14,32 32,32 32,52" fill="#1488d0"/>
|
||||||
|
<polygon points="32,32 50,32 32,52" fill="#105aa0"/>
|
||||||
|
<polygon points="32,12 22,18 32,22" fill="#cdf5ff"/>
|
||||||
|
<polygon points="32,12 50,32 32,52 14,32" fill="none" stroke="#f3d27a" stroke-width="2.2" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 824 B |
23
public/manifest.webmanifest
Normal file
23
public/manifest.webmanifest
Normal file
@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
43
public/sw.js
Normal file
43
public/sw.js
Normal file
@ -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;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
@ -7,7 +7,7 @@ import Account from './Account.jsx';
|
|||||||
import TavernGame from './TavernGame.jsx';
|
import TavernGame from './TavernGame.jsx';
|
||||||
import { api } from './api.js';
|
import { api } from './api.js';
|
||||||
|
|
||||||
const RELEASE = '0.39.0-docs';
|
const RELEASE = '0.40.0-pwa';
|
||||||
|
|
||||||
const NAV = [
|
const NAV = [
|
||||||
{ id: 'home', label: 'Tavern' },
|
{ id: 'home', label: 'Tavern' },
|
||||||
|
|||||||
@ -19,3 +19,9 @@ createRoot(document.getElementById('root')).render(
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 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(() => {}); });
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user