Make SuiviCPT an installable PWA

Adds what iOS/Android need to treat the site as an installable app
from Safari's "Add to Home Screen": a manifest.json, apple-touch-icon
set generated from the existing logo mark (scripts/generate-icons.mjs,
sharp as a dev dependency), and the apple-mobile-web-app-* meta tags
iOS Safari requires since it ignores manifest.json entirely.

The service worker is deliberately minimal — this app shows live
financial data, so navigations always hit the network first. It only
caches enough to serve offline.html when there's no connection, not a
full offline-first app shell.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jeremy bayse
2026-08-03 11:03:37 +02:00
co-authored by Claude Sonnet 5
parent 6a776b0042
commit 5352f39887
15 changed files with 788 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
import sharp from 'sharp';
import { mkdirSync } from 'fs';
const BRAND = '#c67139';
const MARK = '#fff8ee';
function svgIcon({ size, markScale }) {
const r = (316 / 2) * markScale;
const cx = size / 2;
const cy = size / 2;
const scale = (size * markScale) / 316;
return `
<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
<rect width="${size}" height="${size}" fill="${BRAND}"/>
<g transform="translate(${cx - 158 * scale}, ${cy - 158 * scale}) scale(${scale})">
<circle cx="158" cy="158" r="140" stroke="${MARK}" stroke-width="16" fill="none"/>
<path d="M90 196 L134 142 L172 174 L238 96" stroke="${MARK}" stroke-width="18" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<path d="M238 96 L238 138 M238 96 L196 96" stroke="${MARK}" stroke-width="18" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</g>
</svg>`;
}
mkdirSync('public/icons', { recursive: true });
const targets = [
{ file: 'public/icons/apple-touch-icon.png', size: 180, markScale: 0.68 },
{ file: 'public/icons/apple-touch-icon-152.png', size: 152, markScale: 0.68 },
{ file: 'public/icons/apple-touch-icon-167.png', size: 167, markScale: 0.68 },
{ file: 'public/icons/icon-192.png', size: 192, markScale: 0.68 },
{ file: 'public/icons/icon-512.png', size: 512, markScale: 0.68 },
{ file: 'public/icons/icon-maskable-512.png', size: 512, markScale: 0.55 },
];
for (const t of targets) {
await sharp(Buffer.from(svgIcon(t)))
.png()
.toFile(t.file);
console.log(`wrote ${t.file}`);
}