meteo-exam/scripts/gen-icons.mjs
Mathis 2f2f363650 v1.0.0 — redesign complet charte graphique V2
- 8 états météo : arc-en-ciel, ensoleillé, brumeux, nuageux, pluvieux, orageux, tempête, fin du monde
- Fonts : Fredoka (titres) + Nunito (texte), palette V2 (bleu #3B82F6, orange #F59E0B, rouge #EF4444)
- Nav flottante : Météo / Radar / Bilan / Paramètres (La Carte supprimée)
- Thème clair / sombre (toggle dans Paramètres)
- Icône app : papier quadrillé + 0 rouge manuscrit
- Illustrations météo CSS pures (soleil, nuages, pluie, éclair, champignon nucléaire, arc-en-ciel)
- Panel admin centralisé dans Paramètres (flash, classe, évaluations, météo, membres)
- Bottom nav flottante avec safe-area pour ne plus toucher la nav système Android
- Suppression des anciennes vues (cards.ts, map.ts, plus.ts, exam-form.ts)
2026-10-04 22:59:00 +02:00

129 lines
3.9 KiB
JavaScript

/**
* Génère l'icône Météo Exam V2 : papier quadrillé + 0 rouge manuscrit.
*/
import { writeFileSync, mkdirSync } from 'node:fs'
import { resolve } from 'node:path'
import { deflateSync } from 'node:zlib'
const OUT = resolve(import.meta.dirname, '../web/public')
mkdirSync(OUT, { recursive: true })
function crc32(buf) {
const table = []
for (let n = 0; n < 256; n++) {
let c = n
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1
table[n] = c >>> 0
}
let crc = 0xffffffff
for (const b of buf) crc = table[(crc ^ b) & 0xff] ^ (crc >>> 8)
return (crc ^ 0xffffffff) >>> 0
}
function chunk(type, data) {
const len = Buffer.alloc(4)
len.writeUInt32BE(data.length)
const body = Buffer.concat([Buffer.from(type, 'ascii'), data])
const crc = Buffer.alloc(4)
crc.writeUInt32BE(crc32(body))
return Buffer.concat([len, body, crc])
}
function encodePng(width, height, rgba) {
const raw = Buffer.alloc(height * (1 + width * 4))
for (let y = 0; y < height; y++) {
raw[y * (1 + width * 4)] = 0
rgba.copy(raw, y * (1 + width * 4) + 1, y * width * 4, (y + 1) * width * 4)
}
const ihdr = Buffer.alloc(13)
ihdr.writeUInt32BE(width, 0)
ihdr.writeUInt32BE(height, 4)
ihdr[8] = 8
ihdr[9] = 6
return Buffer.concat([
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
chunk('IHDR', ihdr),
chunk('IDAT', deflateSync(raw)),
chunk('IEND', Buffer.alloc(0)),
])
}
function drawIcon(size) {
const buf = Buffer.alloc(size * size * 4)
const paperR = 248, paperG = 250, paperB = 252 // #F8FAFC
const gridR = 185, gridG = 213, gridB = 246 // #B9D5F6
const redR = 239, redG = 68, redB = 68 // #EF4444
const lineR = 236, lineG = 73, lineB = 73 // #EC4949
const cx = size / 2
const cy = size / 2
const gridSize = size / 10
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const i = (y * size + x) * 4
// Fond papier
let r = paperR, g = paperG, b = paperB, a = 255
// Quadrillage
const gx = x % gridSize
const gy = y % gridSize
const nearVx = gx < 1.5 || gx > gridSize - 1.5
const nearHy = gy < 1.5 || gy > gridSize - 1.5
if (nearVx || nearHy) {
r = gridR; g = gridG; b = gridB
}
// Marge rouge verticale (style cahier)
if (x < size * 0.08 && x > size * 0.05) {
r = lineR; g = lineG; b = lineB
}
// Dessiner le "0" rouge (ellipse inclinée)
const dx = (x - cx) / (size * 0.22)
const dy = (y - cy) / (size * 0.3)
const dist0 = Math.sqrt(dx * dx + dy * dy)
// Anneau du 0
if (dist0 > 0.7 && dist0 < 1.15) {
r = redR; g = redG; b = redB
}
// Rayons du soleil (petits traits autour du 0)
const angle = Math.atan2(y - cy, x - cx)
const rayDist = Math.sqrt((x - cx) ** 2 + (y - cy) ** 2)
const rayOuter = size * 0.42
const rayInner = size * 0.36
if (rayDist > rayInner && rayDist < rayOuter) {
// 8 rayons
const rayAngle = (Math.round(angle / (Math.PI / 4)) * Math.PI) / 4
const perpDist = Math.abs((x - cx) * Math.sin(rayAngle) - (y - cy) * Math.cos(rayAngle))
if (perpDist < size * 0.02) {
r = redR; g = redG; b = redB
}
}
// Soulignement double en bas
const underY1 = cy + size * 0.38
const underY2 = cy + size * 0.42
if (Math.abs(y - underY1) < size * 0.015 && x > cx - size * 0.3 && x < cx + size * 0.3) {
r = redR; g = redG; b = redB
}
if (Math.abs(y - underY2) < size * 0.015 && x > cx - size * 0.28 && x < cx + size * 0.32) {
r = redR; g = redG; b = redB
}
buf[i] = r
buf[i + 1] = g
buf[i + 2] = b
buf[i + 3] = a
}
}
return buf
}
for (const size of [192, 512]) {
const png = encodePng(size, size, drawIcon(size))
writeFileSync(resolve(OUT, `icon-${size}.png`), png)
console.log(`icon-${size}.png (${png.length} octets)`)
}