// THE ICON SET, AS DATA — which files exist under /icons/, how links // them, which sizes /favicon.ico packs, and which palette a site's icons are lit // with. PURE (no `next/og`, no I/O), so the layouts, the export header and the // manifest can read it without pulling the renderer into every page's server // graph. The renderers live in lib/brandIcons.ts, which only the icon and // favicon route handlers import. import { childIconPalette, type IconPalette, type MarkVariant } from "./brand"; import { resolveAccent } from "./accent"; export type IconFile = | { file: string; format: "svg"; variant: MarkVariant; contentType: "image/svg+xml" } | { file: string; format: "png"; variant: MarkVariant; size: number; contentType: "image/png" }; // Every file under /icons/, in the order the manifest and want them. // The URLs are a contract: the manifest, both layouts' `icons` metadata, the // service workers' `/icons/` network-first rule, the homepage's OG image and // installed PWAs all name them. export const ICON_FILES: ReadonlyArray = [ { file: "icon.svg", format: "svg", variant: "any", contentType: "image/svg+xml" }, { file: "maskable.svg", format: "svg", variant: "maskable", contentType: "image/svg+xml" }, { file: "icon-32.png", format: "png", variant: "any", size: 32, contentType: "image/png" }, { file: "icon-192.png", format: "png", variant: "any", size: 192, contentType: "image/png" }, { file: "icon-512.png", format: "png", variant: "any", size: 512, contentType: "image/png" }, { file: "maskable-512.png", format: "png", variant: "maskable", size: 512, contentType: "image/png" }, { file: "apple-touch-icon.png", format: "png", variant: "apple", size: 180, contentType: "image/png" }, ]; // The icon links, shared by the export and homepage layouts: the SVG // first (every current browser takes it, and it stays sharp at any size), the // 32 px PNG for tabs that will not, the two app sizes, and the touch icon. // /favicon.ico is served too, but is not linked. (Not typed with Next's // `Metadata`: importing the `next` root types into common adds Next's global // ProcessEnv augmentation to every common test's program.) export const ICON_METADATA = { icon: [ { url: "/icons/icon.svg", type: "image/svg+xml" }, { url: "/icons/icon-32.png", sizes: "32x32", type: "image/png" }, { url: "/icons/icon-192.png", sizes: "192x192", type: "image/png" }, { url: "/icons/icon-512.png", sizes: "512x512", type: "image/png" }, ], apple: [{ url: "/icons/apple-touch-icon.png", sizes: "180x180" }], }; export function iconFile(name: string): IconFile | undefined { return ICON_FILES.find((f) => f.file === name); } // The sizes packed into /favicon.ico. Browsers that still ask for it pick the // entry nearest their tab size. export const FAVICON_SIZES = [16, 32, 48] as const; // The palette an archive site's icons are lit with: the child ground, and its // accent's on-dark value (an absent accent reads as Signal; a custom hex is // fitted to the dark ground). The hub, homepage and editor use // ICON_PALETTES.archilyzer instead. export function siteIconPalette(accent: unknown): IconPalette { return childIconPalette(resolveAccent(accent).dark); }