{"version":3,"file":"tempest-pwa-icons.cjs","names":[],"sources":["../../src/vite/tempest-pwa-icons.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — the plugin generates every icon size,\n * the maskable variants, the favicon and the Apple touch icons from one source\n * image, and wires them into the manifest it also writes. The sizes and the manifest\n * entries are the same list read twice, so they are produced together.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** Options for {@link tempestPwaIcons}. */\nexport interface TempestPwaIconsOptions {\n    /** Source image (SVG or large PNG), relative to the project root. Default `public/icon.svg`. */\n    source?: string;\n    /** Square \"any\"-purpose icon sizes to emit. Default `[192, 512]`. */\n    sizes?: number[];\n    /** Square \"maskable\" icon sizes to emit (with safe-zone padding). Default `[512]`. */\n    maskableSizes?: number[];\n    /** Apple touch icon size, or `false` to skip. Default `180`. */\n    appleTouchIcon?: number | false;\n    /** Output directory for the icon set, under the build root. Default `icons`. */\n    outDir?: string;\n    /** Opaque background for maskable + apple icons (no transparency allowed). Default `#ffffff`. */\n    background?: string;\n    /** Maskable safe-zone padding as a fraction of the icon. Default `0.1` (10% each side). */\n    maskablePadding?: number;\n    /**\n     * Generate Apple splash screens (launch images) and inject the matching\n     * `<link rel=\"apple-touch-startup-image\">` tags. `true` uses a built-in set\n     * of common iPhone/iPad portrait sizes; pass an array to override. Default `false`.\n     */\n    appleSplash?: boolean | AppleSplashSpec[];\n    /** Background color for splash screens. Default: `background`. */\n    splashBackground?: string;\n    /** Icon size on the splash as a fraction of the shorter side. Default `0.3`. */\n    splashIconScale?: number;\n}\n\n/** A single Apple splash target (CSS px + device pixel ratio). */\nexport interface AppleSplashSpec {\n    /** CSS width (device-width in the media query). */\n    width: number;\n    /** CSS height (device-height in the media query). */\n    height: number;\n    /** Device pixel ratio. */\n    ratio: number;\n}\n\n/** Common iPhone/iPad portrait splash sizes (CSS px @ ratio). */\nconst DEFAULT_SPLASH: AppleSplashSpec[] = [\n    { width: 375, height: 667, ratio: 2 }, // iPhone SE / 8\n    { width: 375, height: 812, ratio: 3 }, // iPhone X / 11 Pro\n    { width: 390, height: 844, ratio: 3 }, // iPhone 12 / 13 / 14\n    { width: 393, height: 852, ratio: 3 }, // iPhone 14 Pro / 15\n    { width: 414, height: 896, ratio: 2 }, // iPhone XR / 11\n    { width: 414, height: 896, ratio: 3 }, // iPhone XS Max / 11 Pro Max\n    { width: 428, height: 926, ratio: 3 }, // iPhone 13/14 Pro Max\n    { width: 430, height: 932, ratio: 3 }, // iPhone 15 Pro Max\n    { width: 768, height: 1024, ratio: 2 }, // iPad\n    { width: 834, height: 1194, ratio: 2 }, // iPad Pro 11\"\n    { width: 1024, height: 1366, ratio: 2 }, // iPad Pro 12.9\"\n];\n\nfunction splashFileName(spec: AppleSplashSpec): string {\n    return `splash/apple-splash-${spec.width * spec.ratio}x${spec.height * spec.ratio}.png`;\n}\n\nfunction splashMedia(spec: AppleSplashSpec): string {\n    return (\n        `(device-width: ${spec.width}px) and (device-height: ${spec.height}px) ` +\n        `and (-webkit-device-pixel-ratio: ${spec.ratio}) and (orientation: portrait)`\n    );\n}\n\ninterface Rgb {\n    r: number;\n    g: number;\n    b: number;\n}\n\nfunction hexToRgb(hex: string): Rgb {\n    const value = hex.replace(\"#\", \"\");\n    const full =\n        value.length === 3\n            ? value\n                  .split(\"\")\n                  .map((c) => c + c)\n                  .join(\"\")\n            : value;\n    return {\n        r: parseInt(full.slice(0, 2), 16),\n        g: parseInt(full.slice(2, 4), 16),\n        b: parseInt(full.slice(4, 6), 16),\n    };\n}\n\n/**\n * Build plugin that rasterizes a single source image into a full PWA icon set\n * (regular + maskable + apple-touch-icon), the dependency-free counterpart to\n * `@vite-pwa/assets-generator`. Rendering uses **`sharp`**, imported lazily and\n * treated as optional: if it isn't installed the plugin logs a warning and skips\n * generation (your build still succeeds; the icons just aren't produced).\n *\n * Point your `manifest.webmanifest` icon entries at the emitted files\n * (`/icons/icon-192.png`, `/icons/icon-512.png`, `/icons/maskable-512.png`) and\n * the apple touch icon at `/apple-touch-icon.png`.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestPwaIcons } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n *   plugins: [tempestPwaIcons({ source: \"public/icon.svg\" })],\n * });\n */\nexport function tempestPwaIcons(options: TempestPwaIconsOptions = {}): TempestVitePlugin {\n    const {\n        source = \"public/icon.svg\",\n        sizes = [192, 512],\n        maskableSizes = [512],\n        appleTouchIcon = 180,\n        outDir = \"icons\",\n        background = \"#ffffff\",\n        maskablePadding = 0.1,\n        appleSplash = false,\n        splashBackground,\n        splashIconScale = 0.3,\n    } = options;\n\n    const splashSpecs: AppleSplashSpec[] = appleSplash\n        ? Array.isArray(appleSplash)\n            ? appleSplash\n            : DEFAULT_SPLASH\n        : [];\n\n    let root = process.cwd();\n\n    const plugin: Plugin = {\n        name: \"tempest-pwa-icons\",\n        apply: \"build\",\n        configResolved(config) {\n            root = config.root ?? process.cwd();\n        },\n        transformIndexHtml() {\n            if (!splashSpecs.length) return;\n            return splashSpecs.map((spec) => ({\n                tag: \"link\",\n                attrs: {\n                    rel: \"apple-touch-startup-image\",\n                    media: splashMedia(spec),\n                    href: `/${splashFileName(spec)}`,\n                },\n                injectTo: \"head\" as const,\n            }));\n        },\n        async generateBundle() {\n            let sharp: SharpFactory;\n            try {\n                // Non-literal specifier so TS doesn't require `sharp`'s types\n                // (it is an optional, lazily-loaded dependency).\n                const specifier = \"sharp\";\n                const mod = (await import(specifier)) as { default?: SharpFactory } & SharpFactory;\n                sharp = (mod.default ?? mod) as SharpFactory;\n            } catch {\n                this.warn(\n                    \"tempestPwaIcons: `sharp` is not installed — skipping icon generation. \" +\n                        \"Run `npm i -D sharp` to enable it.\",\n                );\n                return;\n            }\n\n            const input = await readFile(resolve(root, source));\n            const bg = hexToRgb(background);\n            const emit = (fileName: string, data: Buffer): void => {\n                this.emitFile({ type: \"asset\", fileName, source: data });\n            };\n\n            // Regular \"any\" icons — transparent background, full bleed.\n            for (const size of sizes) {\n                const png = await sharp(input, { density: Math.max(size, 512) })\n                    .resize(size, size, {\n                        fit: \"contain\",\n                        background: { r: 0, g: 0, b: 0, alpha: 0 },\n                    })\n                    .png()\n                    .toBuffer();\n                emit(`${outDir}/icon-${size}.png`, png);\n            }\n\n            // Maskable icons — content shrunk into the safe zone over a solid bg.\n            for (const size of maskableSizes) {\n                const content = Math.round(size * (1 - maskablePadding * 2));\n                const png = await sharp(input, { density: Math.max(size, 512) })\n                    .resize(content, content, {\n                        fit: \"contain\",\n                        background: { r: 0, g: 0, b: 0, alpha: 0 },\n                    })\n                    .extend({\n                        top: Math.round((size - content) / 2),\n                        bottom: Math.round((size - content) / 2),\n                        left: Math.round((size - content) / 2),\n                        right: Math.round((size - content) / 2),\n                        background: { ...bg, alpha: 1 },\n                    })\n                    .resize(size, size)\n                    .png()\n                    .toBuffer();\n                emit(`${outDir}/maskable-${size}.png`, png);\n            }\n\n            // Apple touch icon — opaque, no alpha.\n            if (appleTouchIcon) {\n                const png = await sharp(input, { density: Math.max(appleTouchIcon, 512) })\n                    .resize(appleTouchIcon, appleTouchIcon, {\n                        fit: \"contain\",\n                        background: { ...bg, alpha: 1 },\n                    })\n                    .flatten({ background: bg })\n                    .png()\n                    .toBuffer();\n                emit(\"apple-touch-icon.png\", png);\n            }\n\n            // Apple splash screens — icon centered on a solid background.\n            if (splashSpecs.length) {\n                const splashBg = hexToRgb(splashBackground ?? background);\n                for (const spec of splashSpecs) {\n                    const w = spec.width * spec.ratio;\n                    const h = spec.height * spec.ratio;\n                    const iconPx = Math.round(Math.min(w, h) * splashIconScale);\n                    const icon = await sharp(input, { density: Math.max(iconPx, 512) })\n                        .resize(iconPx, iconPx, {\n                            fit: \"contain\",\n                            background: { r: 0, g: 0, b: 0, alpha: 0 },\n                        })\n                        .png()\n                        .toBuffer();\n                    const png = await sharp({\n                        create: {\n                            width: w,\n                            height: h,\n                            channels: 4,\n                            background: { ...splashBg, alpha: 1 },\n                        },\n                    })\n                        .composite([{ input: icon, gravity: \"center\" }])\n                        .png()\n                        .toBuffer();\n                    emit(splashFileName(spec), png);\n                }\n            }\n        },\n    };\n\n    return plugin as TempestVitePlugin;\n}\n\n/** Options for the sharp `create` (blank canvas) form. */\ninterface SharpCreate {\n    create: {\n        width: number;\n        height: number;\n        channels: number;\n        background: { r: number; g: number; b: number; alpha: number };\n    };\n}\n\n/** The sharp factory function (minimal typing — sharp is an optional dep). */\ntype SharpFactory = (input: Buffer | SharpCreate, opts?: { density?: number }) => SharpInstance;\n\n/** Minimal subset of the sharp chainable API this plugin uses. */\ninterface SharpInstance {\n    resize(\n        width: number,\n        height: number,\n        opts?: { fit?: string; background?: { r: number; g: number; b: number; alpha: number } },\n    ): SharpInstance;\n    extend(opts: {\n        top: number;\n        bottom: number;\n        left: number;\n        right: number;\n        background: { r: number; g: number; b: number; alpha: number };\n    }): SharpInstance;\n    flatten(opts: { background: Rgb }): SharpInstance;\n    composite(items: { input: Buffer; gravity?: string }[]): SharpInstance;\n    png(): SharpInstance;\n    toBuffer(): Promise<Buffer>;\n}\n"],"mappings":"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"}