Something went wrong. Try again.
a tiny pet with a GPT-2 brain, living in your browser
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788// Writes the app icons in site/assets/ from the 16x16 face in the page header.// Usage: node site/make-icons.mjs (no dependencies; rerun after changing the art)import { writeFileSync } from "node:fs";import { deflateSync } from "node:zlib";
// The header SVG's paths: rectilinear outlines in 16x16 pixel units.const LAYERS = [ ["#4a3b4f", "M5 2h6v1h2v2h1v6h-1v2h-2v1H5v-1H3v-2H2V5h1V3h2z"], ["#ffd6e2", "M5 3h6v1h1v2h1v5h-1v1h-1v1H5v-1H4v-1H3V6h1V4h1z"], ["#4a3b4f", "M5 7h2v2H5zm4 0h2v2H9zM7 10h2v1H7z"], ["#f2668f", "M4 10h1v1H4zm7 0h1v1h-1z"],];const SHELL = "#ffc9d8", EDGE = "#f3a4bb";
function polygons(d) { const polys = []; let x = 0, y = 0, cur = null; for (const [, c, n] of d.matchAll(/([MmHhVvz])\s*([-\d.]*(?:\s+[-\d.]+)?)/g)) { const v = n.trim().split(/\s+/).map(Number); if (c === "M" || c === "m") { x = c === "M" ? v[0] : x + v[0]; y = c === "M" ? v[1] : y + v[1]; cur = [[x, y]]; polys.push(cur); } else if (c === "h") { x += v[0]; cur.push([x, y]); } else if (c === "H") { x = v[0]; cur.push([x, y]); } else if (c === "v") { y += v[0]; cur.push([x, y]); } else if (c === "V") { y = v[0]; cur.push([x, y]); } else if (c === "z") { x = cur[0][0]; y = cur[0][1]; } } return polys;}function inside(poly, px, py) { let hit = false; for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) { const [xi, yi] = poly[i], [xj, yj] = poly[j]; if ((yi > py) !== (yj > py) && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) hit = !hit; } return hit;}const rgb = (h) => [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16));const grid = Array.from({ length: 16 }, () => Array(16).fill(null));for (const [color, d] of LAYERS) { const polys = polygons(d); for (let j = 0; j < 16; j++) for (let i = 0; i < 16; i++) if (polys.some((p) => inside(p, i + 0.5, j + 0.5))) grid[j][i] = color;}
// size: output pixels; face: share of the icon the face spans; round: corner// radius as a share of the size (0 = full bleed, for maskable and Apple icons).function icon(size, face, round) { const px = Math.floor((size * face) / 16), off = Math.floor((size - px * 16) / 2); const rows = []; for (let y = 0; y < size; y++) { const row = [0]; for (let x = 0; x < size; x++) { let a = 255, c = rgb(SHELL); if (round) { const r = size * round, dx = Math.max(r - x - 0.5, x + 0.5 - (size - r), 0), dy = Math.max(r - y - 0.5, y + 0.5 - (size - r), 0); if (dx * dx + dy * dy > r * r) a = 0; } if (y >= size * 0.86 && a) c = rgb(EDGE); // the toy's lower rim const i = Math.floor((x - off) / px), j = Math.floor((y - off) / px); if (i >= 0 && i < 16 && j >= 0 && j < 16 && grid[j][i]) c = rgb(grid[j][i]); row.push(...c, a); } rows.push(Buffer.from(row)); } return png(size, size, Buffer.concat(rows));}
function crc32(buf) { let c, crc = 0xffffffff; for (const b of buf) { c = (crc ^ b) & 0xff; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; crc = (crc >>> 8) ^ c; } return (crc ^ 0xffffffff) >>> 0;}function chunk(type, data) { const len = Buffer.alloc(4); len.writeUInt32BE(data.length); const td = Buffer.concat([Buffer.from(type), data]); const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(td)); return Buffer.concat([len, td, crc]);}function png(w, h, raw) { const ihdr = Buffer.alloc(13); ihdr.writeUInt32BE(w, 0); ihdr.writeUInt32BE(h, 4); ihdr[8] = 8; ihdr[9] = 6; // 8-bit RGBA return Buffer.concat([Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), chunk("IHDR", ihdr), chunk("IDAT", deflateSync(raw, { level: 9 })), chunk("IEND", Buffer.alloc(0))]);}
const out = new URL("assets/", import.meta.url);for (const [name, size, face, round] of [["icon-192.png", 192, 0.75, 0.22], ["icon-512.png", 512, 0.75, 0.22], ["icon-maskable-512.png", 512, 0.56, 0], ["icon-180.png", 180, 0.7, 0]]) { writeFileSync(new URL(name, out), icon(size, face, round)); console.log("wrote", name);}