feat: logo/icône custom (monogramme OF voxel)
Badge sombre + "OF" en blocs biseautés (thème vert). electron-builder dérive l'ICO Windows et les PNG Linux depuis build/icon.png. Généré par scripts/gen-logo.mjs (SVG sans dépendance ; --png via resvg). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.6 KiB |
@@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Génère le logo/icône du launcher : monogramme "OF" en style voxel (blocs
|
||||||
|
* biseautés façon Minecraft) sur un badge sombre arrondi, accent vert du thème.
|
||||||
|
*
|
||||||
|
* node scripts/gen-logo.mjs -> build/icon.svg
|
||||||
|
* node scripts/gen-logo.mjs --png -> + build/icon.png (1024px) via @resvg/resvg-js
|
||||||
|
*
|
||||||
|
* electron-builder dérive ensuite l'ICO Windows et les PNG Linux depuis
|
||||||
|
* build/icon.png (directories.buildResources = build).
|
||||||
|
*/
|
||||||
|
import { writeFile } from 'node:fs/promises'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
|
||||||
|
const SIZE = 512
|
||||||
|
const OUT = join(process.cwd(), 'build')
|
||||||
|
|
||||||
|
// Palette (cohérente avec src/renderer/src/index.css).
|
||||||
|
const C = {
|
||||||
|
bgTop: '#1b2330',
|
||||||
|
bgBottom: '#0d1014',
|
||||||
|
border: '#2b3444',
|
||||||
|
base: '#3fb950', // vert accent
|
||||||
|
light: '#74e08a', // arête haut/gauche (lumière)
|
||||||
|
dark: '#218a3b' // arête bas/droite (ombre)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matrices pixel des lettres (1 = bloc plein).
|
||||||
|
const O = ['01110', '10001', '10001', '10001', '10001', '10001', '01110']
|
||||||
|
const F = ['1111', '1000', '1000', '1110', '1000', '1000', '1000']
|
||||||
|
|
||||||
|
const ROWS = 7
|
||||||
|
const GAP_COLS = 1
|
||||||
|
const COLS = O[0].length + GAP_COLS + F[0].length // 5 + 1 + 4 = 10
|
||||||
|
|
||||||
|
const cell = 40 // pas de la grille
|
||||||
|
const blockGap = 6 // espace entre blocs (effet grille pixel)
|
||||||
|
const bs = cell - blockGap // taille d'un bloc
|
||||||
|
const bevel = Math.round(bs * 0.18) // épaisseur du biseau
|
||||||
|
|
||||||
|
const gridW = COLS * cell
|
||||||
|
const gridH = ROWS * cell
|
||||||
|
const x0 = (SIZE - gridW) / 2
|
||||||
|
const y0 = (SIZE - gridH) / 2
|
||||||
|
|
||||||
|
/** Un bloc voxel : base + arêtes claires (haut/gauche) et sombres (bas/droite). */
|
||||||
|
function block(col, row) {
|
||||||
|
const x = x0 + col * cell + blockGap / 2
|
||||||
|
const y = y0 + row * cell + blockGap / 2
|
||||||
|
const t = bevel
|
||||||
|
return [
|
||||||
|
`<rect x="${x}" y="${y}" width="${bs}" height="${bs}" fill="${C.base}"/>`,
|
||||||
|
`<rect x="${x}" y="${y}" width="${bs}" height="${t}" fill="${C.light}"/>`,
|
||||||
|
`<rect x="${x}" y="${y}" width="${t}" height="${bs}" fill="${C.light}"/>`,
|
||||||
|
`<rect x="${x}" y="${y + bs - t}" width="${bs}" height="${t}" fill="${C.dark}"/>`,
|
||||||
|
`<rect x="${x + bs - t}" y="${y}" width="${t}" height="${bs}" fill="${C.dark}"/>`
|
||||||
|
].join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rend une matrice à un décalage de colonnes donné. */
|
||||||
|
function letter(matrix, colOffset) {
|
||||||
|
const out = []
|
||||||
|
matrix.forEach((line, r) => {
|
||||||
|
;[...line].forEach((px, c) => {
|
||||||
|
if (px === '1') out.push(block(colOffset + c, r))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return out.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const blocks = letter(O, 0) + letter(F, O[0].length + GAP_COLS)
|
||||||
|
|
||||||
|
const radius = 112
|
||||||
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="${C.bgTop}"/>
|
||||||
|
<stop offset="1" stop-color="${C.bgBottom}"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="6" y="6" width="${SIZE - 12}" height="${SIZE - 12}" rx="${radius}" ry="${radius}" fill="url(#bg)" stroke="${C.border}" stroke-width="6"/>
|
||||||
|
<g shape-rendering="crispEdges">
|
||||||
|
${blocks}
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await writeFile(join(OUT, 'icon.svg'), svg)
|
||||||
|
console.log('✓ build/icon.svg')
|
||||||
|
|
||||||
|
if (process.argv.includes('--png')) {
|
||||||
|
const { Resvg } = await import('@resvg/resvg-js')
|
||||||
|
const png = new Resvg(svg, { fitTo: { mode: 'width', value: 1024 } })
|
||||||
|
.render()
|
||||||
|
.asPng()
|
||||||
|
await writeFile(join(OUT, 'icon.png'), png)
|
||||||
|
console.log('✓ build/icon.png (1024px)')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => {
|
||||||
|
console.error(e.message)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user