feat: bootstrap lunch picker miniapp with backend, docs, and branding assets

This commit is contained in:
mingking2
2026-04-15 14:03:08 +09:00
commit 7faf251fd3
85 changed files with 31332 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
const fs = require('fs');
const path = require('path');
const { createCanvas } = require('@napi-rs/canvas');
const SIZE = 600;
const outDir = __dirname;
function roundedRect(ctx, x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}
function drawSteam(ctx, x, y, color) {
ctx.strokeStyle = color;
ctx.lineWidth = 10;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(x, y + 32);
ctx.bezierCurveTo(x - 14, y + 6, x + 22, y - 6, x + 8, y - 34);
ctx.stroke();
}
function drawChopsticks(ctx, color) {
ctx.strokeStyle = color;
ctx.lineWidth = 12;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(380, 170);
ctx.lineTo(490, 280);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(415, 150);
ctx.lineTo(525, 260);
ctx.stroke();
}
function drawLogo({
filename,
bgGradientFrom,
bgGradientTo,
cardColor,
cardShadow,
bowlTop,
bowlBottom,
accent,
textColor,
steamColor,
chopColor,
}) {
const canvas = createCanvas(SIZE, SIZE);
const ctx = canvas.getContext('2d');
// background
const bg = ctx.createLinearGradient(0, 0, SIZE, SIZE);
bg.addColorStop(0, bgGradientFrom);
bg.addColorStop(1, bgGradientTo);
ctx.fillStyle = bg;
ctx.fillRect(0, 0, SIZE, SIZE);
// icon card
ctx.save();
ctx.shadowColor = cardShadow;
ctx.shadowBlur = 28;
ctx.shadowOffsetY = 14;
roundedRect(ctx, 72, 72, 456, 456, 120);
ctx.fillStyle = cardColor;
ctx.fill();
ctx.restore();
// bowl gradient
const bowlGrad = ctx.createLinearGradient(200, 270, 400, 420);
bowlGrad.addColorStop(0, bowlTop);
bowlGrad.addColorStop(1, bowlBottom);
// bowl body
ctx.fillStyle = bowlGrad;
roundedRect(ctx, 170, 300, 260, 130, 54);
ctx.fill();
// bowl lip
ctx.fillStyle = 'rgba(255,255,255,0.86)';
ctx.beginPath();
ctx.ellipse(300, 298, 144, 36, 0, 0, Math.PI * 2);
ctx.fill();
// inner bowl
ctx.fillStyle = 'rgba(13,31,60,0.32)';
ctx.beginPath();
ctx.ellipse(300, 300, 76, 18, 0, 0, Math.PI * 2);
ctx.fill();
// noodles
ctx.strokeStyle = accent;
ctx.lineWidth = 9;
ctx.lineCap = 'round';
for (let i = 0; i < 3; i++) {
const y = 282 + i * 8;
ctx.beginPath();
ctx.moveTo(220, y);
ctx.bezierCurveTo(250, y - 14, 350, y - 14, 380, y);
ctx.stroke();
}
// steam
drawSteam(ctx, 245, 215, steamColor);
drawSteam(ctx, 300, 190, steamColor);
drawSteam(ctx, 355, 215, steamColor);
// chopsticks
drawChopsticks(ctx, chopColor);
// JM monogram chip
roundedRect(ctx, 220, 448, 160, 56, 28);
ctx.fillStyle = 'rgba(255,255,255,0.22)';
ctx.fill();
ctx.fillStyle = textColor;
ctx.font = 'bold 34px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('점메추', 300, 476);
const output = path.join(outDir, filename);
fs.writeFileSync(output, canvas.toBuffer('image/png'));
return output;
}
const light = drawLogo({
filename: 'lunch-pick-logo-light-600-v2.png',
bgGradientFrom: '#F7FBFF',
bgGradientTo: '#E8F3FF',
cardColor: 'rgba(255,255,255,0.94)',
cardShadow: 'rgba(30,102,180,0.20)',
bowlTop: '#29B2F4',
bowlBottom: '#0A7FE8',
accent: '#0876D7',
textColor: '#0F3F7C',
steamColor: '#5AAAF6',
chopColor: '#1D4D87',
});
const dark = drawLogo({
filename: 'lunch-pick-logo-dark-600-v2.png',
bgGradientFrom: '#061226',
bgGradientTo: '#0D203F',
cardColor: 'rgba(17,39,72,0.95)',
cardShadow: 'rgba(0,0,0,0.45)',
bowlTop: '#56C8FF',
bowlBottom: '#1997F0',
accent: '#9ED9FF',
textColor: '#EAF4FF',
steamColor: '#BFE4FF',
chopColor: '#E6F3FF',
});
console.log(light);
console.log(dark);