// 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);
}