From 17a378bb400ad166d66850cf51de553f4d14d587 Mon Sep 17 00:00:00 2001 From: Dicken Date: Tue, 26 May 2026 11:40:50 +0200 Subject: [PATCH] Dateien nach "frontend" hochladen --- frontend/generate-icons.mjs | 123 ++++++++++++++++++++++++++++-------- 1 file changed, 98 insertions(+), 25 deletions(-) diff --git a/frontend/generate-icons.mjs b/frontend/generate-icons.mjs index 5f2637f..67fb64f 100644 --- a/frontend/generate-icons.mjs +++ b/frontend/generate-icons.mjs @@ -1,33 +1,106 @@ -import { createCanvas } from 'canvas'; +// Generates icon-192.png and icon-512.png without external dependencies +// Uses a minimal PNG encoder written in pure JS import { writeFileSync } from 'fs'; +import { deflateSync } from 'zlib'; -function generateIcon(size) { - const canvas = createCanvas(size, size); - const ctx = canvas.getContext('2d'); - const r = size * 0.22; +function createPNG(size) { + const r = Math.round(size * 0.22); - const grad = ctx.createLinearGradient(0, 0, size, size); - grad.addColorStop(0, '#4ecdc4'); - grad.addColorStop(1, '#ff6b9d'); - ctx.beginPath(); - ctx.moveTo(r, 0); ctx.lineTo(size - r, 0); - ctx.quadraticCurveTo(size, 0, size, r); - ctx.lineTo(size, size - r); - ctx.quadraticCurveTo(size, size, size - r, size); - ctx.lineTo(r, size); ctx.quadraticCurveTo(0, size, 0, size - r); - ctx.lineTo(0, r); ctx.quadraticCurveTo(0, 0, r, 0); - ctx.closePath(); - ctx.fillStyle = grad; - ctx.fill(); + // Draw pixels + const pixels = new Uint8Array(size * size * 4); - ctx.fillStyle = '#0d0d0f'; - ctx.textAlign = 'center'; - ctx.font = `bold ${size * 0.52}px serif`; - ctx.fillText('⚒', size / 2, size * 0.72); + const setPixel = (x, y, R, G, B, A = 255) => { + if (x < 0 || x >= size || y < 0 || y >= size) return; + const i = (y * size + x) * 4; + pixels[i] = R; pixels[i+1] = G; pixels[i+2] = B; pixels[i+3] = A; + }; - return canvas.toBuffer('image/png'); + // Gradient background with rounded corners + for (let y = 0; y < size; y++) { + for (let x = 0; x < size; x++) { + // Rounded corner check + const inCornerTL = x < r && y < r && (x-r)**2 + (y-r)**2 > r**2; + const inCornerTR = x >= size-r && y < r && (x-(size-r))**2 + (y-r)**2 > r**2; + const inCornerBL = x < r && y >= size-r && (x-r)**2 + (y-(size-r))**2 > r**2; + const inCornerBR = x >= size-r && y >= size-r && (x-(size-r))**2 + (y-(size-r))**2 > r**2; + if (inCornerTL || inCornerTR || inCornerBL || inCornerBR) continue; + + // Linear gradient: top-left #4ecdc4 → bottom-right #ff6b9d + const t = (x + y) / (size * 2); + const R = Math.round(0x4e + (0xff - 0x4e) * t); + const G = Math.round(0xcd + (0x6b - 0xcd) * t); + const B = Math.round(0xc4 + (0x9d - 0xc4) * t); + setPixel(x, y, R, G, B); + } + } + + // Draw ⚒ as a simple hammer shape (dark color on gradient) + const cx = Math.floor(size / 2); + const cy = Math.floor(size / 2); + const s = Math.floor(size * 0.18); + + // Hammer head (rectangle) + for (let dy = -s; dy <= 0; dy++) { + for (let dx = -s; dx <= s; dx++) { + setPixel(cx + dx, cy + dy, 13, 13, 15); + } + } + // Handle + for (let dy = 0; dy <= s * 2; dy++) { + for (let dx = -Math.floor(s*0.25); dx <= Math.floor(s*0.25); dx++) { + setPixel(cx + dx, cy + dy, 13, 13, 15); + } + } + + // Encode as PNG + const width = size, height = size; + const signature = Buffer.from([137,80,78,71,13,10,26,10]); + + // IHDR + const ihdr = Buffer.alloc(13); + ihdr.writeUInt32BE(width, 0); ihdr.writeUInt32BE(height, 4); + ihdr[8] = 8; ihdr[9] = 2; // 8-bit RGB... wait we need RGBA + // Use RGBA (color type 6) + ihdr[9] = 6; + const ihdrChunk = makeChunk('IHDR', ihdr); + + // IDAT - raw image data with filter bytes + const raw = Buffer.alloc(height * (1 + width * 4)); + for (let y = 0; y < height; y++) { + raw[y * (1 + width * 4)] = 0; // filter type None + for (let x = 0; x < width; x++) { + const si = (y * width + x) * 4; + const di = y * (1 + width * 4) + 1 + x * 4; + raw[di] = pixels[si]; + raw[di+1] = pixels[si+1]; + raw[di+2] = pixels[si+2]; + raw[di+3] = pixels[si+3] || (pixels[si] || pixels[si+1] || pixels[si+2] ? 255 : 0); + } + } + const compressed = deflateSync(raw); + const idatChunk = makeChunk('IDAT', compressed); + const iendChunk = makeChunk('IEND', Buffer.alloc(0)); + + return Buffer.concat([signature, ihdrChunk, idatChunk, iendChunk]); } -writeFileSync('public/icon-192.png', generateIcon(192)); -writeFileSync('public/icon-512.png', generateIcon(512)); +function makeChunk(type, data) { + const len = Buffer.alloc(4); len.writeUInt32BE(data.length); + const typeB = Buffer.from(type); + const crc = crc32(Buffer.concat([typeB, data])); + const crcB = Buffer.alloc(4); crcB.writeUInt32BE(crc >>> 0); + return Buffer.concat([len, typeB, data, crcB]); +} + +function crc32(buf) { + let crc = 0xFFFFFFFF; + for (const b of buf) { + crc ^= b; + for (let i = 0; i < 8; i++) crc = (crc >>> 1) ^ (crc & 1 ? 0xEDB88320 : 0); + } + return (crc ^ 0xFFFFFFFF) >>> 0; +} + +writeFileSync('public/icon-192.png', createPNG(192)); +writeFileSync('public/icon-512.png', createPNG(512)); console.log('Icons generated.');