import type { APIRoute } from 'astro'; import { brand } from '../lib/brand.mjs'; import { token } from '../lib/tokens.mjs'; /** * The web app manifest. * * It exists because §7's table lists `icon-192.png` and `icon-512.png` as brand files, and * without a manifest nothing ever asks for them — an installed-icon size that no document * references is a file the operator maintains for nobody. * * Prerendered like every other page: the icon URLs it points at are stable and unhashed, so * the manifest does not change when the icons behind them do. The two strings that CAN * change — the name and the description — are handled the same way as the HTML, by * `scripts/applyBrand.mjs` at boot, which is why that script rewrites `.webmanifest` as * well as `.html`. */ export const GET: APIRoute = () => new Response( JSON.stringify( { name: brand.siteName, short_name: brand.siteName, description: brand.tagline, start_url: '/', scope: '/', display: 'standalone', background_color: token('--bg'), theme_color: token('--bg'), icons: [ { src: '/brand/icon-192.png', sizes: '192x192', type: 'image/png' }, { src: '/brand/icon-512.png', sizes: '512x512', type: 'image/png' }, // `purpose: maskable` is a promise that the mark survives being cropped to a // circle or a squircle. `buildBrandAssets.mjs` insets the emblem inside its // canvas for exactly this, but the promise is only true for the STOCK logo — an // operator who mounts edge-to-edge artwork would get it clipped on Android, so // the declaration stays off until the site can know what it is shipping. ], }, null, 2 ), { headers: { 'Content-Type': 'application/manifest+json; charset=utf-8' } } );