Files
shelem/gen-icons.js
T
goyban 8e8478e45b Initial commit: Shelem card game
Full-stack multiplayer Shelem (Iranian trick-taking card game) with
Socket.IO, JWT auth, bot players, joker mode, and mobile-friendly UI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 16:17:37 +00:00

112 lines
3.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
// Generates PNG icons with a Joker card motif on dark green background.
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
const outDir = path.join(__dirname, 'public', 'icons');
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
function writePNG(filePath, size) {
const channels = 4;
const row = size * channels;
const raw = Buffer.alloc(size * row);
// Background: dark forest green #133025
const bgR = 0x13, bgG = 0x30, bgB = 0x25;
// Gold color for Joker diamond: #f5c518
const gR = 0xf5, gG = 0xc5, gB = 0x18;
const cx = size / 2, cy = size / 2;
const dh = size * 0.35; // half-height of diamond
const dw = size * 0.28; // half-width
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const px = (y * size + x) * channels;
const nx = x + 0.5, ny = y + 0.5;
// Rounded corners
const cr = size * 0.2;
const inCorner =
(nx < cr && ny < cr && Math.hypot(nx - cr, ny - cr) > cr) ||
(nx > size - cr && ny < cr && Math.hypot(nx - (size - cr), ny - cr) > cr) ||
(nx < cr && ny > size - cr && Math.hypot(nx - cr, ny - (size - cr)) > cr) ||
(nx > size - cr && ny > size - cr && Math.hypot(nx - (size - cr), ny - (size - cr)) > cr);
if (inCorner) { raw[px + 3] = 0; continue; }
// Draw a diamond / rhombus shape (Joker card symbol)
const dx = Math.abs(nx - cx) / dw;
const dy = Math.abs(ny - cy) / dh;
const inDiamond = dx + dy <= 1;
if (inDiamond) {
raw[px] = gR;
raw[px + 1] = gG;
raw[px + 2] = gB;
raw[px + 3] = 255;
} else {
raw[px] = bgR;
raw[px + 1] = bgG;
raw[px + 2] = bgB;
raw[px + 3] = 255;
}
}
}
const chunks = [];
chunks.push(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
function crc32(buf) {
let c = 0xffffffff;
const table = crc32.table || (crc32.table = (() => {
const t = new Uint32Array(256);
for (let i = 0; i < 256; i++) {
let v = i;
for (let k = 0; k < 8; k++) v = v & 1 ? 0xedb88320 ^ (v >>> 1) : v >>> 1;
t[i] = v;
}
return t;
})());
for (let i = 0; i < buf.length; i++) c = table[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
return (c ^ 0xffffffff) >>> 0;
}
function chunk(type, data) {
const len = Buffer.alloc(4); len.writeUInt32BE(data.length);
const tp = Buffer.from(type);
const crc = Buffer.alloc(4);
crc.writeUInt32BE(crc32(Buffer.concat([tp, data])));
return Buffer.concat([len, tp, data, crc]);
}
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(size, 0); ihdr.writeUInt32BE(size, 4);
ihdr[8] = 8; ihdr[9] = 6;
chunks.push(chunk('IHDR', ihdr));
const filtered = Buffer.alloc(size * (row + 1));
for (let y = 0; y < size; y++) {
filtered[y * (row + 1)] = 0;
raw.copy(filtered, y * (row + 1) + 1, y * row, (y + 1) * row);
}
chunks.push(chunk('IDAT', zlib.deflateSync(filtered)));
chunks.push(chunk('IEND', Buffer.alloc(0)));
fs.writeFileSync(filePath, Buffer.concat(chunks));
console.log(`${path.basename(filePath)} written (${size}×${size})`);
}
writePNG(path.join(outDir, 'icon-192.png'), 192);
writePNG(path.join(outDir, 'icon-512.png'), 512);
// SVG icon (scalable, used as favicon)
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" rx="20" fill="#133025"/>
<polygon points="50,10 80,50 50,90 20,50" fill="#f5c518"/>
<text x="50" y="56" font-size="22" text-anchor="middle" fill="#133025" font-family="serif" font-weight="bold">J</text>
</svg>`;
fs.writeFileSync(path.join(outDir, 'icon.svg'), svg);
console.log('icon.svg written');
console.log('Icons generated.');