/* * Genereert app-icoon, splash-screen, web-favicon en BIMI-logo uit één bron: * src/components/Envelope.jsx. * * node assets/build.mjs # SVG/HTML-bronnen → PNG's → Android-resources * node assets/build.mjs --no-cap # alleen bronnen + PNG's (geen @capacitor/assets) * node assets/build.mjs --only-sources # alleen assets/src/ (geen rasteren, niets buiten die map) * * Gebruikt de tools in ~/.claude/tools: imgconv (SVG → PNG via sharp) en webshot * (splash met echte lettertypen via headless Chromium). Uitvoer: * assets/src/*.svg|html bronnen (gecommit, handig om te bekijken/aan te passen), waaronder * bimi.svg — het logo voor mailclients, zie hieronder * assets/*.png invoer voor @capacitor/assets (icon-only, icon-foreground, * icon-background, splash, splash-dark) * public/favicon.svg, public/apple-touch-icon.png * android/app/src/main/res/** via @capacitor/assets */ import { mkdir, writeFile } from "node:fs/promises"; import { spawnSync } from "node:child_process"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { createServer } from "vite"; import { renderToStaticMarkup } from "react-dom/server"; import { createElement as h } from "react"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const TOOLS = path.join(process.env.USERPROFILE ?? process.env.HOME, ".claude", "tools"); const withCap = !process.argv.includes("--no-cap"); const P = (...p) => path.join(root, ...p); /* ---------- Envelope.jsx laden via Vite (JSX + theme-imports) ---------- */ const vite = await createServer({ root, server: { middlewareMode: true }, logLevel: "silent" }); const { default: Envelope } = await vite.ssrLoadModule("/src/components/Envelope.jsx"); const { INK, PAPER, MUTE, SKY } = await vite.ssrLoadModule("/src/theme.js"); await vite.close(); /* Envelop als losse string, gepositioneerd binnen een groter canvas */ const envelope = (x, y, width, id) => renderToStaticMarkup(h(Envelope, { width, id })).replace(" `\n${inner}\n\n`; /* ---------- BIMI-logo (SVG Tiny 1.2, profiel "tiny-ps") ---------- * Het merkteken dat mailclients naast de afzender zetten als het domein een BIMI-record heeft. * Tiny-ps is een smalle subset van SVG: vierkante viewBox, `version` en `baseProfile` op de * wortel, een `` als eerste kind — en géén geneste , ,
${envelope(0, 0, 900, "splash")}

Liefdesbrieven

PAR AVION

`; await mkdir(P("assets", "src"), { recursive: true }); for (const [name, content] of Object.entries(sources)) await writeFile(P("assets", "src", name), content); await writeFile(P("assets", "src", "splash.html"), splashHtml(PAPER, INK, MUTE)); await writeFile(P("assets", "src", "splash-dark.html"), splashHtml(INK, "#fff", SKY)); /* Previewblad: alles bij elkaar op één pagina (bekijken met webshot) */ await writeFile(P("assets", "src", "preview.html"), `
icon-only (afgerond zoals iOS/launchers dat doen)
adaptive (cirkelmasker) · icon-only.png · favicon 32/16
splash.png
splash-dark.png
`); if (process.argv.includes("--only-sources")) { console.log("klaar (alleen assets/src/)"); process.exit(0); } /* ---------- Rasteren ---------- */ const run = (label, cmd, args, opts = {}) => { const r = spawnSync(cmd, args, { stdio: "pipe", encoding: "utf8", shell: process.platform === "win32", ...opts }); if (r.status !== 0) { console.error(`✗ ${label}\n${r.stdout}\n${r.stderr}`); process.exit(1); } console.log(`✓ ${label}`); }; const imgconv = (input, out, size, extra = []) => run(`imgconv ${path.basename(out)} ${size}px`, "node", [path.join(TOOLS, "imgconv", "imgconv.js"), input, "--out", out, "--width", String(size), "--height", String(size), "--fit", "contain", "--bg", "transparent", ...extra]); const webshot = (input, out) => run(`webshot ${path.basename(out)}`, "node", [path.join(TOOLS, "webshot", "webshot.js"), pathToFileURL(input).href, "--out", out, "--selector", "#splash", "--width", "2732", "--height", "2732", "--wait", "800"]); imgconv(P("assets/src/icon-only.svg"), P("assets/icon-only.png"), 1024); imgconv(P("assets/src/icon-foreground.svg"), P("assets/icon-foreground.png"), 1024); imgconv(P("assets/src/icon-background.svg"), P("assets/icon-background.png"), 1024); imgconv(P("assets/src/icon-only.svg"), P("public/apple-touch-icon.png"), 180); await writeFile(P("public", "favicon.svg"), sources["favicon.svg"]); webshot(P("assets/src/splash.html"), P("assets/splash.png")); webshot(P("assets/src/splash-dark.html"), P("assets/splash-dark.png")); /* ---------- Android-resources ---------- */ if (withCap) { run("@capacitor/assets generate --android", "npx", ["@capacitor/assets", "generate", "--android", "--assetPath", "assets", "--iconBackgroundColor", INK, "--iconBackgroundColorDark", INK, "--splashBackgroundColor", PAPER, "--splashBackgroundColorDark", INK], { cwd: root }); /* * @capacitor/assets zet de adaptive-icon-lagen op 48 dp (xxxhdpi = 192 px); een adaptive icon * hoort 108 dp te zijn (xxxhdpi = 432 px). De launcher blaast ze anders op → wazig icoon. * Daarom hier opnieuw op de juiste maat, uit dezelfde bronnen. */ const DP108 = { ldpi: 81, mdpi: 108, hdpi: 162, xhdpi: 216, xxhdpi: 324, xxxhdpi: 432 }; for (const [dpi, px] of Object.entries(DP108)) { const dir = P("android", "app", "src", "main", "res", `mipmap-${dpi}`); imgconv(P("assets/src/icon-foreground.svg"), path.join(dir, "ic_launcher_foreground.png"), px); imgconv(P("assets/src/icon-background.svg"), path.join(dir, "ic_launcher_background.png"), px); } /* * @capacitor/assets zet in de adaptive-icon-XML een inset van 16,7% op béide lagen: de * achtergrond krijgt dan een transparante (zwart getoonde) rand en de envelop wordt dubbel * klein (de SVG houdt de veilige zone al aan). Daarom de XML zelf schrijven: effen inkt- * achtergrond over de hele laag, voorgrond zonder inset. */ const adaptive = ` `; for (const name of ["ic_launcher.xml", "ic_launcher_round.xml"]) { await writeFile(P("android", "app", "src", "main", "res", "mipmap-anydpi-v26", name), adaptive); } await writeFile(P("android", "app", "src", "main", "res", "values", "ic_launcher_background.xml"), ` ${INK} `); console.log("✓ adaptive-icon XML (zonder inset)"); } console.log("klaar");