/** * Assets brand Ultimail générés depuis le mark vectoriel plat 4 couleurs : * - source : public/ultimail-mark.svg * * Sorties : * - public/brand/ultimail-header-icon.png (288, transparent) * - public/brand/ultimail-mark.{png,jpg} (256) * - public/brand/ultimail-wordmark-stacked.{png,jpg} + -dark.png (800×800) * - public/brand/ultimail-wordmark-horizontal.{png,jpg} (1600×460) * - app/icon.png (32) + app/apple-icon.png (180, fond blanc) * * pnpm run brand:ultimail */ import { mkdirSync, readFileSync } from "node:fs" import { dirname, join } from "node:path" import { fileURLToPath } from "node:url" import sharp from "sharp" const __dirname = dirname(fileURLToPath(import.meta.url)) const root = join(__dirname, "..") const markPath = join(root, "public/ultimail-mark.svg") const brandDir = join(root, "public/brand") const appDir = join(root, "app") const FONT = "Helvetica Neue, Helvetica, Arial, sans-serif" /** Contenu interne du mark (sans la balise ), pour l'inliner dans les lockups. */ function markInnerSvg() { const raw = readFileSync(markPath, "utf8") const open = raw.indexOf(">", raw.indexOf("") return raw.slice(open + 1, close) } /** Wordmark empilé 800×800 : mark + « Ultimail » dessous. */ function stackedSvg(textColor) { const inner = markInnerSvg() return ` ${inner} Ultimail ` } /** Wordmark horizontal 1600×460 : mark à gauche, « Ultimail » à droite. */ function horizontalSvg(textColor) { const inner = markInnerSvg() return ` ${inner} Ultimail ` } async function writeSvgPng(svg, outPath, { background } = {}) { let pipe = sharp(Buffer.from(svg), { density: 144 }) if (background) pipe = pipe.flatten({ background }) await pipe.png({ compressionLevel: 9 }).toFile(outPath) console.log("Wrote", outPath.replace(`${root}/`, "")) } async function writeSvgJpg(svg, outPath) { await sharp(Buffer.from(svg), { density: 144 }) .flatten({ background: "#ffffff" }) .jpeg({ quality: 92, mozjpeg: true }) .toFile(outPath) console.log("Wrote", outPath.replace(`${root}/`, "")) } async function writeMarkPng(outPath, size, { background } = {}) { let pipe = sharp(markPath, { density: 300 }).resize(size, size, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 }, }) if (background) pipe = pipe.flatten({ background }) await pipe.png({ compressionLevel: 9 }).toFile(outPath) console.log("Wrote", outPath.replace(`${root}/`, "")) } async function main() { mkdirSync(brandDir, { recursive: true }) // Picto seul await writeMarkPng(join(brandDir, "ultimail-header-icon.png"), 288) await writeMarkPng(join(brandDir, "ultimail-mark.png"), 256) await sharp(join(brandDir, "ultimail-mark.png")) .flatten({ background: "#ffffff" }) .jpeg({ quality: 92, mozjpeg: true }) .toFile(join(brandDir, "ultimail-mark.jpg")) // Wordmarks await writeSvgPng(stackedSvg("#202124"), join(brandDir, "ultimail-wordmark-stacked.png")) await writeSvgJpg(stackedSvg("#202124"), join(brandDir, "ultimail-wordmark-stacked.jpg")) await writeSvgPng(stackedSvg("#e8eaed"), join(brandDir, "ultimail-wordmark-stacked-dark.png")) await writeSvgPng(horizontalSvg("#202124"), join(brandDir, "ultimail-wordmark-horizontal.png")) await writeSvgJpg(horizontalSvg("#202124"), join(brandDir, "ultimail-wordmark-horizontal.jpg")) // Favicons Next.js (conventions app/) await writeMarkPng(join(appDir, "icon.png"), 32) await writeMarkPng(join(appDir, "apple-icon.png"), 180, { background: "#ffffff" }) } main().catch((e) => { console.error(e) process.exit(1) })