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,150 @@
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 + 36);
ctx.bezierCurveTo(x - 14, y + 10, x + 20, y, x + 6, y - 30);
ctx.stroke();
}
function drawRice(ctx, cx, cy, colorA, colorB) {
// Rice mound
const grad = ctx.createLinearGradient(cx - 100, cy - 30, cx + 100, cy + 20);
grad.addColorStop(0, colorA);
grad.addColorStop(1, colorB);
ctx.fillStyle = grad;
ctx.beginPath();
ctx.ellipse(cx, cy, 128, 56, 0, 0, Math.PI * 2);
ctx.fill();
// Rice grains highlights (subtle)
ctx.fillStyle = 'rgba(255,255,255,0.55)';
for (let i = 0; i < 14; i++) {
const x = cx - 95 + i * 14;
const y = cy - 6 + ((i % 2) * 6 - 3);
ctx.beginPath();
ctx.ellipse(x, y, 4, 2.6, -0.3, 0, Math.PI * 2);
ctx.fill();
}
}
function drawLogo(theme) {
const canvas = createCanvas(SIZE, SIZE);
const ctx = canvas.getContext('2d');
// Background gradient
const bg = ctx.createLinearGradient(0, 0, SIZE, SIZE);
bg.addColorStop(0, theme.bgFrom);
bg.addColorStop(1, theme.bgTo);
ctx.fillStyle = bg;
ctx.fillRect(0, 0, SIZE, SIZE);
// Main card
ctx.save();
ctx.shadowColor = theme.shadow;
ctx.shadowBlur = 28;
ctx.shadowOffsetY = 10;
roundedRect(ctx, 74, 74, 452, 452, 122);
ctx.fillStyle = theme.card;
ctx.fill();
ctx.restore();
// Bowl body
const bowlGrad = ctx.createLinearGradient(180, 280, 420, 430);
bowlGrad.addColorStop(0, theme.bowlTop);
bowlGrad.addColorStop(1, theme.bowlBottom);
ctx.fillStyle = bowlGrad;
roundedRect(ctx, 168, 302, 264, 144, 64);
ctx.fill();
// Bowl lip outer
ctx.fillStyle = theme.lipOuter;
ctx.beginPath();
ctx.ellipse(300, 300, 154, 44, 0, 0, Math.PI * 2);
ctx.fill();
// Bowl lip inner
ctx.fillStyle = theme.lipInner;
ctx.beginPath();
ctx.ellipse(300, 304, 104, 26, 0, 0, Math.PI * 2);
ctx.fill();
// Rice
drawRice(ctx, 300, 278, theme.riceA, theme.riceB);
// Chopsticks (to imply meal)
ctx.strokeStyle = theme.chop;
ctx.lineWidth = 12;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(380, 178);
ctx.lineTo(492, 290);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(416, 158);
ctx.lineTo(528, 270);
ctx.stroke();
// Steam
drawSteam(ctx, 240, 196, theme.steam);
drawSteam(ctx, 300, 172, theme.steam);
drawSteam(ctx, 360, 196, theme.steam);
const output = path.join(outDir, theme.filename);
fs.writeFileSync(output, canvas.toBuffer('image/png'));
return output;
}
const light = drawLogo({
filename: 'lunch-pick-logo-light-600-v3.png',
bgFrom: '#F6FAFF',
bgTo: '#EAF3FF',
card: 'rgba(255,255,255,0.96)',
shadow: 'rgba(16,78,147,0.18)',
bowlTop: '#31B4F4',
bowlBottom: '#0A85E8',
lipOuter: '#DDE9F8',
lipInner: '#8EA6C5',
riceA: '#FFFFFF',
riceB: '#EFF6FF',
chop: '#1E4F8A',
steam: '#5EA8EE',
});
const dark = drawLogo({
filename: 'lunch-pick-logo-dark-600-v3.png',
bgFrom: '#07142A',
bgTo: '#0E2243',
card: 'rgba(17,39,72,0.96)',
shadow: 'rgba(0,0,0,0.46)',
bowlTop: '#66CBFF',
bowlBottom: '#1A9EF4',
lipOuter: '#DDE6F2',
lipInner: '#8CA2BC',
riceA: '#FFFFFF',
riceB: '#F2F7FF',
chop: '#E4F0FF',
steam: '#B8DCFF',
});
console.log(light);
console.log(dark);