// The title screen art. // ---------- Title art ---------- const FONT5 = { D: ['11110', '10001', '10001', '10001', '10001', '10001', '11110'], E: ['11111', '10000', '10000', '11110', '10000', '10000', '11111'], P: ['11110', '10001', '10001', '11110', '10000', '10000', '10000'], G: ['01111', '10000', '10000', '10011', '10001', '10001', '01111'], R: ['11110', '10001', '10001', '11110', '10100', '10010', '10001'], A: ['01110', '10001', '10001', '11111', '10001', '10001', '10001'], I: ['11111', '00100', '00100', '00100', '00100', '00100', '11111'], N: ['10001', '11001', '10101', '10011', '10001', '10001', '10001'], F: ['11111', '10000', '10000', '11110', '10000', '10000', '10000'], O: ['01110', '10001', '10001', '10001', '10001', '10001', '01110'], U: ['10001', '10001', '10001', '10001', '10001', '10001', '01110'], Y: ['10001', '10001', '01010', '00100', '00100', '00100', '00100'], }; const TITLE = { cells: [], grains: [], t: 0, raf: 0 }; function buildTitleCells() { const lines = [['DEEPGRAIN', 6, 'hot'], ['FOUNDRY', 44, 'copper']]; for (const [word, y0, kind] of lines) { const sc = 3, wpx = word.length * 6 * sc - sc; const x0 = Math.round((320 - wpx) / 2); [...word].forEach((ch, k) => { const g = FONT5[ch]; for (let r = 0; r < 7; r++) for (let c = 0; c < 5; c++) if (g[r][c] === '1') for (let a = 0; a < sc; a++) for (let b = 0; b < sc; b++) TITLE.cells.push({ x: x0 + (k * 6 + c) * sc + a, y: y0 + r * sc + b, kind, n: Math.random() }); }); } } function drawTitle() { const c = $('titleart'); if (!c || $('title').hidden) { TITLE.raf = 0; return; } const ctx = c.getContext('2d'); const t = (TITLE.t += 1); const img = ctx.createImageData(320, 80), px = new Uint32Array(img.data.buffer); px.fill(0x00000000); for (const g of TITLE.cells) { const w = 0.55 + 0.45 * Math.sin(t * 0.045 + g.x * 0.09 + g.n * 6); let r, gg, b; if (g.kind === 'hot') { const T = 1050 + w * 650 + g.n * 120; const i = glowIndex(T); r = GLOW_R[i]; gg = GLOW_G[i]; b = GLOW_B[i]; } else { const v = (g.n - 0.5) * 30; r = 200 + v + w * 20; gg = 122 + v + w * 12; b = 76 + v; } px[g.y * 320 + g.x] = 0xff000000 | (Math.max(0, Math.min(255, b)) << 16) | (Math.max(0, Math.min(255, gg)) << 8) | Math.max(0, Math.min(255, r)); } // falling grains under the words if (Math.random() < 0.6) TITLE.grains.push({ x: 60 + Math.random() * 200, y: 28, v: 0.3 + Math.random() * 0.4, hot: Math.random() < 0.6 }); for (let k = TITLE.grains.length - 1; k >= 0; k--) { const g = TITLE.grains[k]; g.y += g.v; g.v += 0.02; if (g.y > 79) { TITLE.grains.splice(k, 1); continue; } if (g.y > 41 && g.y < 66 && px[(g.y | 0) * 320 + (g.x | 0)]) { TITLE.grains.splice(k, 1); continue; } px[(g.y | 0) * 320 + (g.x | 0)] = g.hot ? 0xff3aa0ff : 0xff5a8cc8; } ctx.putImageData(img, 0, 0); TITLE.raf = requestAnimationFrame(drawTitle); }