feat: bootstrap lunch picker miniapp with backend, docs, and branding assets
This commit is contained in:
136
template/assets/branding/generate-logos.js
Normal file
136
template/assets/branding/generate-logos.js
Normal file
@@ -0,0 +1,136 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { PNG } = require('pngjs');
|
||||
|
||||
const SIZE = 600;
|
||||
|
||||
function hexToRgb(hex) {
|
||||
const clean = hex.replace('#', '');
|
||||
return {
|
||||
r: parseInt(clean.substring(0, 2), 16),
|
||||
g: parseInt(clean.substring(2, 4), 16),
|
||||
b: parseInt(clean.substring(4, 6), 16),
|
||||
};
|
||||
}
|
||||
|
||||
function setPixel(png, x, y, color) {
|
||||
if (x < 0 || y < 0 || x >= png.width || y >= png.height) return;
|
||||
const idx = (png.width * y + x) << 2;
|
||||
png.data[idx] = color.r;
|
||||
png.data[idx + 1] = color.g;
|
||||
png.data[idx + 2] = color.b;
|
||||
png.data[idx + 3] = 255;
|
||||
}
|
||||
|
||||
function fillRect(png, x0, y0, x1, y1, color) {
|
||||
const sx = Math.max(0, Math.floor(x0));
|
||||
const sy = Math.max(0, Math.floor(y0));
|
||||
const ex = Math.min(png.width - 1, Math.ceil(x1));
|
||||
const ey = Math.min(png.height - 1, Math.ceil(y1));
|
||||
for (let y = sy; y <= ey; y++) {
|
||||
for (let x = sx; x <= ex; x++) setPixel(png, x, y, color);
|
||||
}
|
||||
}
|
||||
|
||||
function fillCircle(png, cx, cy, r, color) {
|
||||
const rr = r * r;
|
||||
const x0 = Math.floor(cx - r);
|
||||
const x1 = Math.ceil(cx + r);
|
||||
const y0 = Math.floor(cy - r);
|
||||
const y1 = Math.ceil(cy + r);
|
||||
for (let y = y0; y <= y1; y++) {
|
||||
for (let x = x0; x <= x1; x++) {
|
||||
const dx = x - cx;
|
||||
const dy = y - cy;
|
||||
if (dx * dx + dy * dy <= rr) setPixel(png, x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fillEllipse(png, cx, cy, rx, ry, color) {
|
||||
const rx2 = rx * rx;
|
||||
const ry2 = ry * ry;
|
||||
const x0 = Math.floor(cx - rx);
|
||||
const x1 = Math.ceil(cx + rx);
|
||||
const y0 = Math.floor(cy - ry);
|
||||
const y1 = Math.ceil(cy + ry);
|
||||
for (let y = y0; y <= y1; y++) {
|
||||
for (let x = x0; x <= x1; x++) {
|
||||
const dx = x - cx;
|
||||
const dy = y - cy;
|
||||
if ((dx * dx) / rx2 + (dy * dy) / ry2 <= 1) setPixel(png, x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawThickLine(png, x0, y0, x1, y1, thickness, color) {
|
||||
const steps = Math.max(Math.abs(x1 - x0), Math.abs(y1 - y0));
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const t = i / (steps || 1);
|
||||
const x = Math.round(x0 + (x1 - x0) * t);
|
||||
const y = Math.round(y0 + (y1 - y0) * t);
|
||||
fillCircle(png, x, y, thickness / 2, color);
|
||||
}
|
||||
}
|
||||
|
||||
function drawLogo(filename, theme) {
|
||||
const png = new PNG({ width: SIZE, height: SIZE });
|
||||
|
||||
const bg = hexToRgb(theme.bg);
|
||||
const card = hexToRgb(theme.card);
|
||||
const bowl = hexToRgb(theme.bowl);
|
||||
const accent = hexToRgb(theme.accent);
|
||||
|
||||
fillRect(png, 0, 0, SIZE - 1, SIZE - 1, bg);
|
||||
|
||||
// Card background shape
|
||||
fillRect(png, 80, 80, 520, 520, card);
|
||||
fillCircle(png, 80, 80, 70, card);
|
||||
fillCircle(png, 520, 80, 70, card);
|
||||
fillCircle(png, 80, 520, 70, card);
|
||||
fillCircle(png, 520, 520, 70, card);
|
||||
|
||||
// Bowl
|
||||
fillEllipse(png, 300, 350, 165, 90, bowl);
|
||||
fillRect(png, 135, 350, 465, 430, bowl);
|
||||
fillEllipse(png, 300, 430, 165, 45, bowl);
|
||||
|
||||
// Bowl top cut
|
||||
fillEllipse(png, 300, 340, 175, 68, card);
|
||||
fillEllipse(png, 300, 346, 175, 4, accent);
|
||||
|
||||
// Noodles
|
||||
fillEllipse(png, 250, 372, 50, 12, accent);
|
||||
fillEllipse(png, 300, 374, 55, 13, accent);
|
||||
fillEllipse(png, 350, 372, 50, 12, accent);
|
||||
|
||||
// Steam
|
||||
fillEllipse(png, 245, 255, 14, 32, accent);
|
||||
fillEllipse(png, 300, 235, 16, 38, accent);
|
||||
fillEllipse(png, 355, 255, 14, 32, accent);
|
||||
|
||||
// Chopsticks
|
||||
drawThickLine(png, 360, 205, 485, 330, 11, accent);
|
||||
drawThickLine(png, 390, 188, 515, 313, 11, accent);
|
||||
|
||||
const outPath = path.join(__dirname, filename);
|
||||
fs.writeFileSync(outPath, PNG.sync.write(png));
|
||||
return outPath;
|
||||
}
|
||||
|
||||
const light = drawLogo('lunch-pick-logo-light-600.png', {
|
||||
bg: '#F8FAFC',
|
||||
card: '#E0F2FE',
|
||||
bowl: '#0EA5E9',
|
||||
accent: '#0C4A6E',
|
||||
});
|
||||
|
||||
const dark = drawLogo('lunch-pick-logo-dark-600.png', {
|
||||
bg: '#0B1220',
|
||||
card: '#13233C',
|
||||
bowl: '#38BDF8',
|
||||
accent: '#E2E8F0',
|
||||
});
|
||||
|
||||
console.log(light);
|
||||
console.log(dark);
|
||||
Reference in New Issue
Block a user