Mother Box prop

Test code

<!DOCTYPE html>
<html lang=”en”>
<head>
    <meta charset=”UTF-8″>
    <meta name=”viewport” content=”width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no”>
    <title>Mother Box – Ultimate Edition</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #050505;
            touch-action: none;
            user-select: none;
            -webkit-user-select: none;
            font-family: ‘Impact’, sans-serif;
        }
        canvas {
            display: block;
        }
        #init-overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.95);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 100;
            flex-direction: column;
            transition: opacity 0.5s;
        }
        #init-btn {
            background: #ffcc00;
            border: 4px solid #000;
            padding: 20px 40px;
            font-size: 24px;
            font-family: ‘Impact’, sans-serif;
            letter-spacing: 2px;
            text-transform: uppercase;
            cursor: pointer;
            box-shadow: 10px 10px 0 #e62e2e;
            animation: pulseBtn 1s infinite alternate;
        }
        @keyframes pulseBtn {
            from { transform: scale(1); }
            to { transform: scale(1.05); }
        }
        .subtitle {
            color: #e62e2e;
            margin-top: 20px;
            font-size: 14px;
            letter-spacing: 1px;
            text-transform: uppercase;
        }
    </style>
</head>
<body>

    <div id=”init-overlay”>
        <div id=”init-btn”>Initialize Mother Box</div>
        <div class=”subtitle”>Touch Screen for Ping</div>
    </div>
    <canvas id=”c”></canvas>

    <script>
        const canvas = document.getElementById(‘c’);
        const ctx = canvas.getContext(‘2d’);

        // — Configuration —
        let width, height;
        let frame = 0;
        let active = false;

        // “Fourth World” Palette
        const colors = {
            gold: ‘#ffc400’,
            goldDim: ‘#b08d00’,
            red: ‘#e61919’,
            redDark: ‘#7a0000’,
            blue: ‘#0066cc’,
            blueDark: ‘#002a5c’,
            black: ‘#000000’,
            white: ‘#ffffff’,
            energy: ‘#ff00ff’,
            cyan: ‘#00ffff’
        };

        // — Audio System —
        const Audio = {
            ctx: null,
            masterGain: null,
           
            init: function() {
                this.ctx = new (window.AudioContext || window.webkitAudioContext)();
                this.masterGain = this.ctx.createGain();
                this.masterGain.connect(this.ctx.destination);
                this.masterGain.gain.value = 0.4;
                this.startHum();
            },

            startHum: function() {
                // Deep, throbbing machine hum
                const osc = this.ctx.createOscillator();
                const gain = this.ctx.createGain();
                osc.type = ‘sawtooth’;
                osc.frequency.value = 48;
               
                const filter = this.ctx.createBiquadFilter();
                filter.type = ‘lowpass’;
                filter.frequency.value = 100;

                const lfo = this.ctx.createOscillator();
                lfo.type = ‘sine’;
                lfo.frequency.value = 0.25;
                const lfoGain = this.ctx.createGain();
                lfoGain.gain.value = 0.08;
               
                lfo.connect(lfoGain);
                lfoGain.connect(gain.gain);
               
                osc.connect(filter);
                filter.connect(gain);
                gain.connect(this.masterGain);
                gain.gain.value = 0.25;
               
                osc.start();
                lfo.start();
            },

            playBloop: function() {
                if(!this.ctx) return;
                const t = this.ctx.currentTime;
                const osc = this.ctx.createOscillator();
                const gain = this.ctx.createGain();
                osc.connect(gain);
                gain.connect(this.masterGain);
               
                osc.type = ‘square’;
                const freq = 600 + Math.random() * 800;
                osc.frequency.setValueAtTime(freq, t);
               
                gain.gain.setValueAtTime(0.03, t);
                gain.gain.exponentialRampToValueAtTime(0.001, t + 0.15);
               
                osc.start(t);
                osc.stop(t + 0.15);
            },

            playPing: function() {
                if(!this.ctx) return;
                const t = this.ctx.currentTime;
               
                // Clear Bell Tone
                const osc = this.ctx.createOscillator();
                const gain = this.ctx.createGain();
                osc.connect(gain);
                gain.connect(this.masterGain);
               
                osc.type = ‘sine’;
                osc.frequency.setValueAtTime(2217, t);
               
                gain.gain.setValueAtTime(0, t);
                gain.gain.linearRampToValueAtTime(0.7, t + 0.02);
                gain.gain.exponentialRampToValueAtTime(0.001, t + 1.5);
               
                osc.start(t);
                osc.stop(t + 1.5);

                // Lower Harmonic
                const osc2 = this.ctx.createOscillator();
                const gain2 = this.ctx.createGain();
                osc2.connect(gain2);
                gain2.connect(this.masterGain);
               
                osc2.type = ‘sine’;
                osc2.frequency.setValueAtTime(1108, t);
               
                gain2.gain.setValueAtTime(0, t);
                gain2.gain.linearRampToValueAtTime(0.25, t + 0.02);
                gain2.gain.exponentialRampToValueAtTime(0.001, t + 1.0);
               
                osc2.start(t);
                osc2.stop(t + 1.0);
            }
        };

        // — Drawing Primitives —
       
        function drawHeavyRect(x, y, w, h, fill) {
            ctx.fillStyle = fill;
            ctx.fillRect(x, y, w, h);
            ctx.lineWidth = 4;
            ctx.strokeStyle = colors.black;
            ctx.strokeRect(x, y, w, h);
        }

        function drawRivets(x, y, w, h) {
            ctx.fillStyle = colors.black;
            const rSz = 5;
            ctx.fillRect(x+3, y+3, rSz, rSz);
            ctx.fillRect(x+w-3-rSz, y+3, rSz, rSz);
            ctx.fillRect(x+3, y+h-3-rSz, rSz, rSz);
            ctx.fillRect(x+w-3-rSz, y+h-3-rSz, rSz, rSz);
        }

        function drawKrackleDots(x, y, w, h, count=8) {
            ctx.fillStyle = colors.black;
            for(let i=0; i<count; i++) {
                const dx = x + Math.random()*(w-4);
                const dy = y + Math.random()*(h-4);
                const r = 3 + Math.random() * 5;
                ctx.beginPath(); ctx.arc(dx, dy, r, 0, Math.PI*2); ctx.fill();
            }
        }

        // — Tile System —
       
        class Tile {
            constructor(c, r, type) {
                this.c = c;
                this.r = r;
                this.type = type;
                this.active = false;
                this.seed = Math.random();
                this.rotation = Math.floor(Math.random() * 4);
            }

            draw(x, y, w, h) {
                // Base Plate
                drawHeavyRect(x, y, w, h, colors.gold);

                const m = 6; // margin for inner content
                const ix = x+m, iy = y+m, iw = w-m*2, ih = h-m*2;

                // Dispatch to specific draw function
                switch(this.type) {
                    case ‘pipes’: this.drawPipes(ix, iy, iw, ih); break;
                    case ‘circuit’: this.drawCircuit(ix, iy, iw, ih); break;
                    case ‘vents’: this.drawVents(ix, iy, iw, ih); break;
                    case ‘krackle_pit’: this.drawKracklePit(ix, iy, iw, ih); break;
                    case ‘machinery’: this.drawMachinery(ix, iy, iw, ih); break;
                    case ‘logic_node’: this.drawLogicNode(ix, iy, iw, ih); break;
                    case ‘energy_fins’: this.drawEnergyFins(ix, iy, iw, ih); break;
                    case ‘omega_block’: this.drawOmegaBlock(ix, iy, iw, ih); break;
                    default: this.drawPlate(ix, iy, iw, ih); break;
                }

                // Finish outer edge
                drawRivets(x, y, w, h);
            }

            // 1. PIPES
            drawPipes(x,y,w,h) {
                ctx.fillStyle = colors.redDark;
                ctx.fillRect(x,y,w,h);
                drawKrackleDots(x,y,w,h, 6); // Energy leak
               
                const count = 3;
                const ph = h/count;
                for(let i=0; i<count; i++) {
                    const py = y + i*ph;
                    // Pipe Body
                    ctx.fillStyle = colors.gold;
                    ctx.fillRect(x, py+3, w, ph-6);
                    ctx.strokeRect(x, py+3, w, ph-6);
                    // Highlight
                    ctx.fillStyle = ‘rgba(255,255,255,0.4)’;
                    ctx.fillRect(x, py+5, w, (ph-6)*0.3);
                   
                    // Pulse
                    if(this.active) {
                        const pulseX = (frame*4 + i*60) % w;
                        ctx.fillStyle = colors.white;
                        ctx.fillRect(x+pulseX, py+3, 15, ph-6);
                        ctx.strokeRect(x+pulseX, py+3, 15, ph-6);
                    }
                }
            }

            // 2. CIRCUIT
            drawCircuit(x,y,w,h) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(x,y,w,h);
                // Grid lines
                ctx.strokeStyle = colors.blueDark;
                ctx.lineWidth = 2;
                ctx.beginPath();
                for(let i=10; i<w; i+=10) { ctx.moveTo(x+i, y); ctx.lineTo(x+i, y+h); }
                ctx.stroke();

                ctx.strokeStyle = this.active ? colors.cyan : colors.gold;
                ctx.lineWidth = 5;
                ctx.lineCap = ‘square’;
                ctx.lineJoin = ‘miter’;
                ctx.beginPath();
               
                // Random-looking jagged path
                const pathY = y + h * (0.3 + this.seed*0.4);
                ctx.moveTo(x, pathY);
                ctx.lineTo(x + w*0.3, pathY);
                ctx.lineTo(x + w*0.3, pathY + (this.seed>0.5 ? 20 : -20));
                ctx.lineTo(x + w*0.7, pathY + (this.seed>0.5 ? 20 : -20));
                ctx.lineTo(x + w*0.7, pathY);
                ctx.lineTo(x + w, pathY);
                ctx.stroke();
               
                // Nodes
                if(this.active) {
                    ctx.fillStyle = colors.white;
                    ctx.beginPath(); ctx.arc(x+w*0.5, pathY + (this.seed>0.5 ? 20 : -20), 4, 0, Math.PI*2); ctx.fill();
                    if(Math.random() > 0.96) Audio.playBloop();
                }
            }

            // 3. VENTS
            drawVents(x,y,w,h) {
                ctx.fillStyle = colors.blueDark;
                ctx.fillRect(x,y,w,h);
                const count = 5;
                const vh = h/count;
                for(let i=0; i<count; i++) {
                    ctx.fillStyle = colors.black;
                    ctx.fillRect(x+4, y + i*vh + 4, w-8, vh-6);
                    // Glowing ember inside vent
                    if(this.active) {
                        ctx.fillStyle = colors.energy;
                        ctx.fillRect(x+10, y + i*vh + 6, Math.random()*(w-20), 2);
                    }
                }
            }

            // 4. KRACKLE PIT
            drawKracklePit(x,y,w,h) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(x,y,w,h);
                // Inset
                const ins = 4;
                ctx.fillStyle = this.active ? colors.energy : colors.redDark;
                ctx.fillRect(x+ins, y+ins, w-ins*2, h-ins*2);
                ctx.strokeRect(x+ins, y+ins, w-ins*2, h-ins*2);
               
                drawKrackleDots(x+ins, y+ins, w-ins*2, h-ins*2, 15);
               
                // Floating blob
                if(this.active) {
                    const bx = x + w/2 + Math.sin(frame*0.1 + this.seed)*5;
                    const by = y + h/2 + Math.cos(frame*0.13)*5;
                    const br = w * 0.25;
                    ctx.fillStyle = colors.white;
                    ctx.beginPath(); ctx.arc(bx, by, br, 0, Math.PI*2); ctx.fill();
                    // Dots inside blob
                    ctx.fillStyle = colors.black;
                    ctx.beginPath(); ctx.arc(bx-3, by-3, 4, 0, Math.PI*2); ctx.fill();
                    ctx.beginPath(); ctx.arc(bx+4, by+2, 5, 0, Math.PI*2); ctx.fill();
                }
            }

            // 5. MACHINERY (New)
            drawMachinery(x,y,w,h) {
                // Background
                ctx.fillStyle = colors.redDark;
                ctx.fillRect(x,y,w,h);
               
                // Random blocky shapes piled up
                ctx.fillStyle = colors.goldDim;
                ctx.lineWidth = 3;
               
                // Block 1
                ctx.fillRect(x+5, y+h*0.4, w*0.6, h*0.5);
                ctx.strokeRect(x+5, y+h*0.4, w*0.6, h*0.5);
               
                // Block 2
                ctx.fillStyle = colors.gold;
                ctx.fillRect(x+w*0.4, y+10, w*0.5, h*0.6);
                ctx.strokeRect(x+w*0.4, y+10, w*0.5, h*0.6);
               
                // Cylinder
                ctx.fillStyle = colors.black;
                ctx.fillRect(x+w*0.5, y+h*0.3, 10, h*0.4);
               
                if(this.active) {
                    ctx.fillStyle = colors.red;
                    ctx.beginPath(); ctx.arc(x+w*0.65, y+30, 4, 0, Math.PI*2); ctx.fill();
                }
            }

            // 6. LOGIC NODE (New)
            drawLogicNode(x,y,w,h) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(x,y,w,h);
               
                // Radiating lines
                ctx.strokeStyle = colors.blueDark;
                ctx.lineWidth = 2;
                const cx = x + w/2;
                const cy = y + h/2;
               
                ctx.beginPath();
                ctx.moveTo(x, y); ctx.lineTo(cx, cy);
                ctx.moveTo(x+w, y); ctx.lineTo(cx, cy);
                ctx.moveTo(x, y+h); ctx.lineTo(cx, cy);
                ctx.moveTo(x+w, y+h); ctx.lineTo(cx, cy);
                ctx.stroke();

                // Central Hub
                const r = w * 0.25;
                ctx.fillStyle = colors.blueDark;
                ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI*2); ctx.fill();
                ctx.stroke(); // Black outline
               
                // Active core
                ctx.fillStyle = this.active ? colors.cyan : ‘#003344’;
                ctx.beginPath(); ctx.arc(cx, cy, r*0.6, 0, Math.PI*2); ctx.fill();
               
                if(this.active) {
                    ctx.fillStyle = colors.white;
                    ctx.fillRect(cx-2, cy-2, 4, 4);
                }
            }

            // 7. ENERGY FINS (New)
            drawEnergyFins(x,y,w,h) {
                ctx.fillStyle = colors.redDark;
                ctx.fillRect(x,y,w,h);
               
                const fins = 4;
                const fw = w / fins;
               
                for(let i=0; i<fins; i++) {
                    const fx = x + i*fw;
                    // Fin body
                    ctx.fillStyle = colors.gold;
                    ctx.fillRect(fx+2, y+5, fw-4, h-10);
                    ctx.strokeRect(fx+2, y+5, fw-4, h-10);
                   
                    // Energy Glow between
                    if(this.active && i < fins-1) {
                         ctx.fillStyle = colors.energy;
                         const pulseH = h * (0.2 + Math.random()*0.6);
                         ctx.fillRect(fx+fw-2, y + (h-pulseH)/2, 4, pulseH);
                    }
                }
            }

            // 8. OMEGA BLOCK (New)
            drawOmegaBlock(x,y,w,h) {
                ctx.fillStyle = colors.goldDim;
                ctx.fillRect(x,y,w,h);
               
                // Heavy black geometric glyph
                ctx.fillStyle = colors.black;
               
                if(this.seed < 0.33) {
                    // The “Omega” shapeish
                    ctx.beginPath();
                    ctx.moveTo(x+10, y+h-10);
                    ctx.lineTo(x+10, y+h*0.4);
                    ctx.lineTo(x+w*0.3, y+10);
                    ctx.lineTo(x+w*0.7, y+10);
                    ctx.lineTo(x+w-10, y+h*0.4);
                    ctx.lineTo(x+w-10, y+h-10);
                    ctx.lineTo(x+w*0.7, y+h-10);
                    ctx.lineTo(x+w*0.7, y+h*0.5);
                    ctx.lineTo(x+w*0.3, y+h*0.5);
                    ctx.lineTo(x+w*0.3, y+h-10);
                    ctx.fill();
                } else if (this.seed < 0.66) {
                    // Zig Zag Block
                    ctx.beginPath();
                    ctx.moveTo(x+5, y+5);
                    ctx.lineTo(x+w-5, y+5);
                    ctx.lineTo(x+5, y+h-5);
                    ctx.lineTo(x+w-5, y+h-5);
                    ctx.lineTo(x+w-5, y+h-15);
                    ctx.lineTo(x+25, y+h-15);
                    ctx.lineTo(x+w-25, y+15);
                    ctx.lineTo(x+5, y+15);
                    ctx.fill();
                } else {
                    // Heavy Circle
                    ctx.beginPath();
                    ctx.arc(x+w/2, y+h/2, w*0.35, 0, Math.PI*2);
                    ctx.fill();
                    ctx.fillStyle = this.active ? colors.red : colors.gold;
                    ctx.beginPath();
                    ctx.arc(x+w/2, y+h/2, w*0.15, 0, Math.PI*2);
                    ctx.fill();
                }
            }

            // 9. PLATE (Varied)
            drawPlate(x,y,w,h) {
                ctx.fillStyle = colors.goldDim;
                ctx.fillRect(x,y,w,h);
               
                ctx.strokeStyle = colors.black;
                ctx.lineWidth = 3;
               
                // Diagonal hatch
                if(this.seed > 0.5) {
                    ctx.beginPath();
                    ctx.moveTo(x+w*0.5, y);
                    ctx.lineTo(x, y+h*0.5);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.moveTo(x+w, y+h*0.5);
                    ctx.lineTo(x+w*0.5, y+h);
                    ctx.stroke();
                } else {
                    // Square inset
                    ctx.strokeRect(x+12, y+12, w-24, h-24);
                }
            }
        }

        // — Grid Logic —
        let grid = [];
        const COLS = 4;
        const ROWS = 5;
        let cellW, cellH, pad;
        let eyeTarget = {x:0, y:0};
        let pingState = { active: false, life: 0 };

        function initGrid() {
            grid = [];
           
            // 1. Create a “Deck” of tile types to shuffle
            // We need 16 tiles (20 total – 4 for eye)
            const deck = [
                ‘pipes’, ‘pipes’,
                ‘circuit’, ‘circuit’,
                ‘vents’, ‘vents’,
                ‘krackle_pit’, ‘krackle_pit’,
                ‘machinery’, ‘machinery’,
                ‘logic_node’, ‘logic_node’,
                ‘energy_fins’, ‘energy_fins’,
                ‘omega_block’, ‘omega_block’,
                ‘plate’, ‘plate’ // Spares
            ];
           
            // Shuffle deck
            for (let i = deck.length – 1; i > 0; i–) {
                const j = Math.floor(Math.random() * (i + 1));
                [deck[i], deck[j]] = [deck[j], deck[i]];
            }

            let deckIndex = 0;

            for(let r=0; r<ROWS; r++) {
                for(let c=0; c<COLS; c++) {
                    let type;
                    // Eye Housing check (center 2×2)
                    if((r===1 || r===2) && (c===1 || c===2)) {
                        type = ‘eye_housing’;
                    } else {
                        type = deck[deckIndex % deck.length];
                        deckIndex++;
                    }
                    grid.push(new Tile(c, r, type));
                }
            }
        }

        function resize() {
            width = canvas.width = window.innerWidth;
            height = canvas.height = window.innerHeight;
            pad = 8;
            cellW = (width – (pad * (COLS+1))) / COLS;
            cellH = (height – (pad * (ROWS+1))) / ROWS;
        }

        function autonomousUpdate() {
            if(frame % 50 === 0) {
                // Randomly toggle a tile
                const t = grid[Math.floor(Math.random() * grid.length)];
                if(t.type !== ‘eye_housing’) {
                    t.active = !t.active;
                }
            }
           
            // Eye drift
            const time = Date.now() * 0.0005;
            eyeTarget.x = (width/2) + Math.sin(time) * (width/3);
            eyeTarget.y = (height/2) + Math.cos(time*1.3) * (height/3);
        }

        function triggerPing() {
            pingState.active = true;
            pingState.life = 1.0;
            if(active) Audio.playPing();
        }

        function loop() {
            frame++;
            autonomousUpdate();

            // Background
            ctx.fillStyle = colors.redDark;
            ctx.fillRect(0, 0, width, height);

            // Draw Tiles
            grid.forEach(t => {
                if(t.type === ‘eye_housing’) {
                    const x = pad + t.c*(cellW+pad);
                    const y = pad + t.r*(cellH+pad);
                    ctx.fillStyle = colors.red;
                    ctx.fillRect(x, y, cellW, cellH);
                    // Tech detail inside eye housing
                    ctx.fillStyle = colors.black;
                    ctx.fillRect(x, y + cellH/2 – 4, cellW, 8);
                    ctx.fillRect(x + cellW/2 – 4, y, 8, cellH);
                    return;
                }
                const x = pad + t.c*(cellW+pad);
                const y = pad + t.r*(cellH+pad);
                t.draw(x, y, cellW, cellH);
            });

            // Draw Central Eye
            const eyeX = pad + 1*(cellW+pad);
            const eyeY = pad + 1*(cellH+pad);
            const eyeW = (cellW*2) + pad;
            const eyeH = (cellH*2) + pad;
           
            ctx.lineWidth = 10;
            ctx.strokeStyle = colors.black;
            ctx.strokeRect(eyeX, eyeY, eyeW, eyeH);
           
            const lensX = eyeX + eyeW/2;
            const lensY = eyeY + eyeH/2;
            const lensR = Math.min(eyeW, eyeH) * 0.38;

            ctx.fillStyle = colors.black;
            ctx.beginPath(); ctx.arc(lensX, lensY, lensR+6, 0, Math.PI*2); ctx.fill();

            // Pulsing Lens
            const pulse = pingState.active ? Math.sin(frame*0.8)*8 : 0;
            ctx.fillStyle = pingState.active ? colors.white : colors.gold;
            ctx.beginPath(); ctx.arc(lensX, lensY, lensR+pulse, 0, Math.PI*2); ctx.fill();
            ctx.lineWidth = 4; ctx.strokeStyle = colors.black; ctx.stroke();

            // Pupil
            const dx = (eyeTarget.x – lensX) * 0.12;
            const dy = (eyeTarget.y – lensY) * 0.12;
            const dist = Math.sqrt(dx*dx + dy*dy);
            const maxD = lensR*0.55;
            let finalDx = dx; let finalDy = dy;
            if(dist > maxD) {
                const angle = Math.atan2(dy, dx);
                finalDx = Math.cos(angle) * maxD;
                finalDy = Math.sin(angle) * maxD;
            }
            ctx.fillStyle = colors.black;
            ctx.beginPath(); ctx.arc(lensX + finalDx, lensY + finalDy, lensR*0.25, 0, Math.PI*2); ctx.fill();
            ctx.fillStyle = colors.white;
            ctx.beginPath(); ctx.arc(lensX + finalDx + lensR*0.08, lensY + finalDy – lensR*0.08, lensR*0.08, 0, Math.PI*2); ctx.fill();

            // Ping Visual
            if(pingState.active) {
                ctx.save();
                ctx.translate(width/2, height/2);
               
                const s = 1 + Math.sin((1-pingState.life)*Math.PI)*0.25;
                ctx.scale(s, s);

                // Jagged Burst
                ctx.beginPath();
                const pts = 24;
                for(let i=0; i<pts*2; i++) {
                    const a = (i/(pts*2)) * Math.PI*2;
                    const baseR = (i%2===0) ? 300 : 180;
                    const r = baseR + Math.random()*20;
                    ctx.lineTo(Math.cos(a)*r, Math.sin(a)*r);
                }
                ctx.closePath();
               
                ctx.fillStyle = colors.black;
                ctx.shadowColor = colors.black;
                ctx.shadowOffsetX = 15; ctx.shadowOffsetY = 15;
                ctx.fill();
               
                ctx.shadowColor = ‘transparent’;
                ctx.fillStyle = colors.white;
                ctx.fill();
               
                ctx.lineWidth = 8;
                ctx.strokeStyle = colors.black;
                ctx.stroke();

                // Inner Krackle
                drawKrackleDots(-100, -100, 200, 200, 20);

                // Text
                ctx.font = “italic 900 130px Impact”;
                ctx.textAlign = “center”;
                ctx.textBaseline = “middle”;
                ctx.fillStyle = colors.red;
                ctx.fillText(“PING!”, 0, 0);
                ctx.lineWidth = 4;
                ctx.strokeStyle = colors.black;
                ctx.strokeText(“PING!”, 0, 0);

                ctx.restore();
                pingState.life -= 0.02;
                if(pingState.life <= 0) pingState.active = false;
            }

            requestAnimationFrame(loop);
        }

        document.getElementById(‘init-btn’).addEventListener(‘click’, () => {
            active = true;
            Audio.init();
            document.getElementById(‘init-overlay’).style.opacity = 0;
            setTimeout(() => {
                document.getElementById(‘init-overlay’).style.display = ‘none’;
                triggerPing();
            }, 500);
        });

        canvas.addEventListener(‘mousedown’, () => triggerPing());
        canvas.addEventListener(‘touchstart’, (e) => { e.preventDefault(); triggerPing(); }, {passive: false});

        window.addEventListener(‘resize’, resize);
        resize();
        initGrid();
        loop();

    </script>
</body>
</html>

Leave a Reply