// Pure JS only — creates a canvas and draws 4 bio-manufacturing incident memes (2×2)
(function drawBioMemeGrid() {
// create canvas and attach to body
const canvas = document.createElement(‘canvas’);
canvas.style.display = ‘block’;
canvas.style.margin = ’18px auto’;
document.body.appendChild(canvas);
// sizes
const W = 1200, H = 900;
const DPR = window.devicePixelRatio || 1;
canvas.width = W * DPR;
canvas.height = H * DPR;
canvas.style.width = W + ‘px’;
canvas.style.height = H + ‘px’;
const ctx = canvas.getContext(‘2d’);
ctx.scale(DPR, DPR);
ctx.font = ’20px sans-serif’;
// helper: rounded rect
function roundRect(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();
}
// panel grid
const rows = 2, cols = 2;
const pad = 20;
const panelW = (W – pad * (cols + 1)) / cols;
const panelH = (H – pad * (rows + 1)) / rows;
// draw panel base
const bgColors = [‘#FFF1B8′,’#E6E6E6′,’#CFE8FF’,’#FFE6D6′]; // warm + cool
for (let r=0; r
const x = pad + c*(panelW + pad);
const y = pad + r*(panelH + pad);
// background
ctx.fillStyle = bgColors[i % bgColors.length];
roundRect(ctx, x, y, panelW, panelH, 12);
ctx.fill();
// border
ctx.lineWidth = 3;
ctx.strokeStyle = ‘#222’;
ctx.stroke();
}
}
// common text draw
function drawTitle(x,y,text){
ctx.fillStyle = ‘#111’;
ctx.font = ‘bold 36px “Noto Sans SC”, sans-serif’;
ctx.fillText(text, x, y);
}
function drawCaption(x,y,text){
ctx.fillStyle = ‘#111’;
ctx.font = ’20px “Noto Sans SC”, sans-serif’;
ctx.fillText(text, x, y);
}
// Panel 1: 灭菌大作战 (top-left)
(function(){
const x = pad, y = pad;
drawTitle(x+20, y+50, ‘灭菌大作战’);
drawCaption(x+20, y+90, ‘你以为是奶茶杯,其实是培养基’);
// draw autoclave (simple)
const cx = x + panelW – 220, cy = y + 160;
// body
ctx.fillStyle = ‘#A6A6A6’;
roundRect(ctx, cx-80, cy-60, 160, 130, 10);
ctx.fill();
ctx.strokeStyle = ‘#555’;
ctx.lineWidth = 2;
ctx.stroke();
// door circle
ctx.beginPath();
ctx.arc(cx, cy+5, 50, 0, Math.PI*2);
ctx.fillStyle = ‘#DADADA’;
ctx.fill();
ctx.stroke();
// bottle inside
ctx.beginPath();
ctx.rect(cx-20, cy-30, 40, 60);
ctx.fillStyle = ‘#FFF’;
ctx.fill();
ctx.stroke();
// cute face on bottle
ctx.fillStyle = ‘#222’;
ctx.fillRect(cx-8, cy-12, 4, 4);
ctx.fillRect(cx+4, cy-12, 4, 4);
ctx.beginPath();
ctx.arc(cx, cy+8, 6, 0, Math.PI);
ctx.stroke();
})();
// Panel 2: 细胞工厂罢工了 (top-right)
(function(){
const x = pad + (panelW + pad), y = pad;
drawTitle(x+20, y+50, ‘细胞工厂罢工了’);
drawCaption(x+20, y+90, ‘没有 FBS,我们不干活!’);
// draw protesting cell (cartoon bean)
const cx = x + panelW – 260, cy = y + 200;
// body
ctx.beginPath();
ctx.ellipse(cx, cy, 60, 90, 0, 0, Math.PI*2);
ctx.fillStyle = ‘#FFD37E’;
ctx.fill();
ctx.strokeStyle = ‘#9B6B00’;
ctx.lineWidth = 3;
ctx.stroke();
// face
ctx.fillStyle = ‘#333’;
ctx.fillRect(cx-18, cy-10, 8, 8);
ctx.fillRect(cx+10, cy-10, 8, 8);
ctx.beginPath();
ctx.moveTo(cx-18, cy+25);
ctx.lineTo(cx+18, cy+25);
ctx.stroke();
// sign
ctx.fillStyle = ‘#fff’;
ctx.strokeStyle = ‘#222’;
ctx.lineWidth = 2;
ctx.fillRect(cx-120, cy-80, 120, 60);
ctx.strokeRect(cx-120, cy-80, 120, 60);
ctx.beginPath();
ctx.moveTo(cx-40, cy-20);
ctx.lineTo(cx-40, cy+40);
ctx.stroke();
ctx.font = ’16px sans-serif’;
ctx.fillStyle = ‘#111’;
ctx.fillText(‘没有FBS’, cx-110, cy-48);
ctx.fillText(‘我们不干活’, cx-110, cy-28);
})();
// Panel 3: 冷链崩坏跑酷 (bottom-left)
(function(){
const x = pad, y = pad + (panelH + pad);
drawTitle(x+20, y+50, ‘冷链崩坏跑酷’);
drawCaption(x+20, y+90, ‘抱着冰箱狂奔,收集干冰块!’);
// courier running holding fridge
const cx = x + panelW – 260, cy = y + 220;
// legs (running)
ctx.strokeStyle = ‘#05386B’;
ctx.lineWidth = 10;
ctx.beginPath();
ctx.moveTo(cx-20, cy+70);
ctx.lineTo(cx-40, cy+100);
ctx.moveTo(cx+20, cy+70);
ctx.lineTo(cx+40, cy+100);
ctx.stroke();
// body
ctx.fillStyle = ‘#05668D’;
ctx.beginPath();
ctx.ellipse(cx, cy+20, 26, 38, 0, 0, Math.PI*2);
ctx.fill();
// head
ctx.fillStyle = ‘#FFD7A6’;
ctx.beginPath();
ctx.arc(cx-10, cy-20, 18, 0, Math.PI*2);
ctx.fill();
ctx.strokeStyle = ‘#333’;
ctx.stroke();
// fridge (box)
ctx.fillStyle = ‘#FFFFFF’;
ctx.fillRect(cx+40, cy-60, 90, 120);
ctx.strokeStyle = ‘#888’;
ctx.strokeRect(cx+40, cy-60, 90, 120);
// dry ice pieces (collectibles)
ctx.fillStyle = ‘#CFE8FF’;
for (let i=0;i<4;i++){
ctx.beginPath();
ctx.arc(x+80 + i*40, y+panelH-90 – (i%2)*10, 12, 0, Math.PI*2);
ctx.fill();
ctx.stroke();
}
})();
// Panel 4: mRNA脱逃记 (bottom-right)
(function(){
const x = pad + (panelW + pad), y = pad + (panelH + pad);
drawTitle(x+20, y+50, ‘mRNA脱逃记’);
drawCaption(x+20, y+90, ‘RNase:专抓裸奔的 mRNA’);
// running mRNA (yellow stick)
const mx = x + panelW – 320, my = y + 210;
ctx.fillStyle = ‘#FFD36E’;
ctx.beginPath();
ctx.ellipse(mx-40, my, 26, 38, 0, 0, Math.PI*2);
ctx.fill();
ctx.strokeStyle = ‘#333’;
ctx.stroke();
// face
ctx.fillStyle = ‘#222’;
ctx.fillRect(mx-52, my-10, 6, 6);
ctx.fillRect(mx-28, my-10, 6, 6);
// RNase monster (green)
const gx = mx+80, gy = my+10;
ctx.fillStyle = ‘#8FD694’;
ctx.beginPath();
ctx.arc(gx, gy, 44, 0, Math.PI*2);
ctx.fill();
ctx.strokeStyle = ‘#4B8F3A’;
ctx.lineWidth = 3;
ctx.stroke();
// spikes
for (let a=0;a<10;a++){
const ang = a*(Math.PI*2/10);
const sx = gx + Math.cos(ang)*58;
const sy = gy + Math.sin(ang)*58;
ctx.beginPath();
ctx.moveTo(gx + Math.cos(ang)*44, gy + Math.sin(ang)*44);
ctx.lineTo(sx, sy);
ctx.stroke();
}
// angry face
ctx.fillStyle = ‘#222’;
ctx.fillRect(gx-20, gy-12, 8, 8);
ctx.fillRect(gx+12, gy-12, 8, 8);
ctx.beginPath();
ctx.moveTo(gx-18, gy+18);
ctx.lineTo(gx+18, gy+6);
ctx.stroke();
})();
// bottom label
ctx.fillStyle = ‘#000’;
ctx.font = ’18px sans-serif’;
ctx.fillText(‘生物制造事故梗图 • 4 格演示(纯 JS)’, 18, H – 10);
// Done
})();