(name)
| 147 | } |
| 148 | |
| 149 | async function generate(name) { |
| 150 | const cfg = APPS[name]; |
| 151 | if (!cfg) { |
| 152 | throw new Error( |
| 153 | `Unknown app "${name}". Known: ${Object.keys(APPS).join(", ")}`, |
| 154 | ); |
| 155 | } |
| 156 | const svg = await loadLogo(); |
| 157 | const outDir = join(REPO_ROOT, cfg.outDir); |
| 158 | await mkdir(outDir, { recursive: true }); |
| 159 | const write = (file, buf) => writeFile(join(outDir, file), buf); |
| 160 | |
| 161 | // Favicons: the lotus stands alone on transparent and tiny — bright mark. |
| 162 | for (const size of FAVICON_SIZES) { |
| 163 | await write( |
| 164 | `favicon-${size}x${size}.png`, |
| 165 | await renderFavicon(svg, size, MARK_COLOR), |
| 166 | ); |
| 167 | } |
| 168 | await write("favicon.ico", await renderFavicon(svg, 32, MARK_COLOR)); |
| 169 | |
| 170 | // "any" PWA icons: transparent standalone lotus (app icon) — bright mark. |
| 171 | for (const size of PWA_SIZES) { |
| 172 | await write( |
| 173 | `icon-${size}.png`, |
| 174 | await renderPaddedIcon(svg, size, MARK_COLOR), |
| 175 | ); |
| 176 | } |
| 177 | |
| 178 | // Apple touch icons: washed field behind a dark lotus, so iOS never renders |
| 179 | // the logo on a black square. |
| 180 | const solid = { |
| 181 | bg: FIELD_COLOR, |
| 182 | logoColor: CONTRAST_COLOR, |
| 183 | fraction: 0.65, |
| 184 | }; |
| 185 | for (const [size, file] of APPLE) { |
| 186 | await write(file, await renderSolidIcon(svg, size, solid)); |
| 187 | } |
| 188 | |
| 189 | // Maskable PWA icon: washed field + extra safe-zone margin. |
| 190 | if (cfg.manifest || cfg.maskable) { |
| 191 | await write( |
| 192 | "icon-maskable-512.png", |
| 193 | await renderSolidIcon(svg, 512, { ...solid, fraction: 0.6 }), |
| 194 | ); |
| 195 | } |
| 196 | |
| 197 | // OG card: lotus stacked above the logotype on a washed field — the dark mark |
| 198 | // does the contrast work, and stacking stays inside the central square so small |
| 199 | // link-preview thumbnails crop to 1:1 without losing the name. Both pieces are |
| 200 | // modular brand atoms, composed here — nothing extracted at render time. |
| 201 | if (cfg.og) { |
| 202 | const text = await loadText(); |
| 203 | await write( |
| 204 | "og-image.png", |
| 205 | await renderOg(svg, text, { |
| 206 | bg: FIELD_COLOR, |
no test coverage detected