Silo: pixel-art version for the pixel look

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 23:16:27 -04:00
co-authored by Claude Opus 5.5
parent edcdf2f900
commit 995ecaa6fb
5 changed files with 71 additions and 2 deletions
+40
View File
@@ -102,6 +102,7 @@ function drawMachines() {
// The silo: legs and braces, a cone to a slide gate, a ribbed steel drum with a ladder and a sight
// glass that shows how full it is, a roof with the loading hatch. All in grain units from its corner.
function drawSilo(ctx, mm, sx, sy, zp) {
if (!R.smoothOn) { drawSiloPixel(ctx, mm, sx, sy, zp); return; }
const X = u => sx + u * zp, Y = u => sy + u * zp, full = Math.min(1, (mm.sn || 0) / SILO_CAP);
const steel = (x0, x1) => { const g = ctx.createLinearGradient(X(x0), 0, X(x1), 0); g.addColorStop(0, '#3b4249'); g.addColorStop(0.22, '#a9b3bb'); g.addColorStop(0.5, '#87919a'); g.addColorStop(1, '#353c43'); return g; };
ctx.save();
@@ -155,3 +156,42 @@ function drawSilo(ctx, mm, sx, sy, zp) {
ctx.beginPath(); ctx.moveTo(X(8.9), Y(0.9)); ctx.lineTo(X(10.2), Y(-1.4)); ctx.stroke();
ctx.restore();
}
function drawSiloPixel(ctx, mm, sx, sy, zp) {
const full = Math.min(1, (mm.sn || 0) / SILO_CAP), col = `rgb(${COLR[mm.sm]},${COLG[mm.sm]},${COLB[mm.sm]})`;
const grain = (x, y, c) => { ctx.fillStyle = c; ctx.fillRect(sx + x * zp, sy + y * zp, zp, zp); };
// Steel shaded by column (light on the left, dark at the right edge), darker rib rows, and fixed
// grain noise so it never shimmers. The drum is rows 2-19, the cone 20-22 narrowing to the gate.
const steel = [[78,86,94],[176,186,194],[160,170,179],[148,158,167],[140,150,159],[134,144,153],
[126,136,145],[118,128,137],[108,118,127],[96,105,114],[84,93,101],[62,70,78]];
for (let y = 2; y < 23; y++) for (let x = 0; x < 12; x++) {
if (y >= 20 && (x < y - 19 || x > 30 - y)) continue;
const c = steel[x], h = Math.imul(mm.x + x, 374761393) ^ Math.imul(mm.y + y, 668265263);
const n = (((h ^ h >>> 13) >>> 0) % 3 - 1) * 4 - (y === 6 || y === 11 || y === 15 || y === 19 ? 26 : 0) - (y >= 20 ? 10 : 0)
- (x === 2 && y < 19 && !(y & 1) ? 34 : 0); // ladder rungs up the left side
grain(x, y, `rgb(${c[0] + n},${c[1] + n},${c[2] + n})`);
}
// legs with a strut to the cone, the gate opening, and the slide gate (pulled aside when open)
for (let y = 20; y < 24; y++) { grain(0, y, '#2f353b'); grain(11, y, '#2f353b'); }
grain(1, 21, '#2f353b'); grain(10, 21, '#2f353b');
for (let x = 4; x < 8; x++) grain(x, 23, '#1c1f23');
if (mm.open) for (let x = 8; x < 11; x++) grain(x, 23, '#c99a3c'); else for (let x = 3; x < 9; x++) grain(x, 23, '#6b737b');
// the sight glass fills from the bottom in the colour of what is inside
for (let y = 3; y < 19; y++) grain(9, y, mm.sn > 0 && y >= 19 - Math.max(1, Math.round(16 * full)) ? col : '#14181c');
// roof: a rim, shoulders, the dark hatch and its lid standing open
for (let x = 0; x < 12; x++) {
grain(x, 1, x >= 3 && x < 9 ? '#15181b' : '#5b646c');
if (x < 3 || x >= 9) grain(x, 0, '#7d878f');
}
const lid = tick - (mm.fillT || -99) < 10 ? '#e0b43c' : '#9aa4ae';
grain(8, 0, lid); grain(9, -1, lid);
if (zp >= 3) {
if (!R.monoFont) R.monoFont = getComputedStyle(document.documentElement).getPropertyValue('--font-mono').trim() || 'monospace';
ctx.save(); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = 'rgba(20,24,28,0.75)';
ctx.font = `700 ${Math.max(8, Math.round(zp * 1.15))}px ${R.monoFont}`;
ctx.fillText('SILO', sx + 5.6 * zp, sy + 5 * zp);
ctx.font = `600 ${Math.max(8, Math.round(zp * 0.95))}px ${R.monoFont}`;
ctx.fillText(mm.sn ? mm.sn.toLocaleString('en-US') : 'EMPTY', sx + 5.6 * zp, sy + 16.8 * zp);
if (mm.sn) { grain(4, 18, col); grain(5, 18, col); grain(6, 18, col); }
ctx.restore();
}
}