/** * Assets branding UltiSuite (SSO Authentik + favicons frontend). * * Sorties : * - ulti-backend/deploy/authentik/branding/ultisuite-logo-{light,dark}.png * - ulti-backend/deploy/authentik/branding/ultisuite-favicon{,-light,-dark}.png * - public/ultisuite-32.png, public/ultisuite-180.png (favicon / apple-touch suite) * * pnpm run brand:authentik */ 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/ultisuite-mark.svg") const publicDir = join(root, "public") const outDir = join(root, "../ulti-backend/deploy/authentik/branding") /** Contenu interne du mark (sans la balise ), pour l'inliner dans le lockup. */ function markInnerSvg() { const raw = readFileSync(markPath, "utf8") const open = raw.indexOf(">", raw.indexOf("") return raw.slice(open + 1, close) } /** * Lockup horizontal : mark + « UltiSuite » (texte plat, deux tons). * `ulti` / `suite` = couleurs du texte selon le thème. */ function lockupSvg({ ulti, suite }) { const inner = markInnerSvg() return ` ${inner} UltiSuite ` } async function writeLockup(outPath, colors) { const buf = Buffer.from(lockupSvg(colors)) await sharp(buf, { density: 144 }) .trim({ threshold: 1 }) .extend({ top: 8, bottom: 8, left: 8, right: 8, background: { r: 0, g: 0, b: 0, alpha: 0 } }) .png({ compressionLevel: 9 }) .toFile(outPath) } async function writeFavicon(outPath, { size = 32, bg } = {}) { const iconSize = Math.round(size * 0.84) const iconBuf = await sharp(markPath) .resize(iconSize, iconSize, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 }, }) .png() .toBuffer() const background = bg === "light" ? { r: 255, g: 255, b: 255, alpha: 1 } : bg === "dark" ? { r: 24, g: 24, b: 27, alpha: 1 } : { r: 0, g: 0, b: 0, alpha: 0 } const offset = Math.round((size - iconSize) / 2) await sharp({ create: { width: size, height: size, channels: 4, background }, }) .composite([{ input: iconBuf, left: offset, top: offset }]) .png({ compressionLevel: 9 }) .toFile(outPath) } async function main() { mkdirSync(outDir, { recursive: true }) // Authentik (SSO de toute la suite) await writeLockup(join(outDir, "ultisuite-logo-light.png"), { ulti: "#202124", suite: "#4285F4", }) await writeLockup(join(outDir, "ultisuite-logo-dark.png"), { ulti: "#e8eaed", suite: "#8ab4f8", }) await writeFavicon(join(outDir, "ultisuite-favicon.png")) await writeFavicon(join(outDir, "ultisuite-favicon-light.png"), { bg: "light" }) await writeFavicon(join(outDir, "ultisuite-favicon-dark.png"), { bg: "dark" }) // Frontend (favicon PNG fallback + apple-touch de la suite) await writeFavicon(join(publicDir, "ultisuite-32.png")) await writeFavicon(join(publicDir, "ultisuite-180.png"), { size: 180, bg: "light" }) console.log("Wrote Authentik branding to", outDir) console.log(" ultisuite-logo-light.png / ultisuite-logo-dark.png") console.log(" ultisuite-favicon.png (+ light/dark variants)") console.log("Wrote public/ultisuite-32.png and public/ultisuite-180.png") } main().catch((e) => { console.error(e) process.exit(1) })