import init, { MatrixEngine } from "./matrix_engine.js"; let engine; let wasm; let ctx; let atlasCanvas, atlasCtx; let width, height; let fontSize = 16; let isMobile = false, isVpnOn = false, isDarkMode = true; const chars = "0101アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホ마미무메모야유요라릴레로와원>_±÷×=≠≈≡≤≥"; function buildAtlas() { // Создаем или пересоздаем атлас под текущий fontSize atlasCanvas = new OffscreenCanvas(chars.length * fontSize, fontSize * 2); atlasCtx = atlasCanvas.getContext("2d", { alpha: false }); let r, g, b; if (isVpnOn && isDarkMode) { r = 139; g = 61; b = 255; } else if (!isVpnOn && isDarkMode) { r = 0; g = 229; b = 242; } else if (isVpnOn && !isDarkMode) { r = 109; g = 40; b = 217; } else { r = 2; g = 132; b = 199; } atlasCtx.fillStyle = isDarkMode ? "rgb(10, 10, 12)" : "rgb(250, 250, 252)"; atlasCtx.fillRect(0, 0, atlasCanvas.width, atlasCanvas.height); atlasCtx.font = `bold ${fontSize}px monospace`; atlasCtx.textBaseline = "top"; atlasCtx.textAlign = "center"; const halfFs = fontSize / 2; atlasCtx.fillStyle = `rgb(${r}, ${g}, ${b})`; for (let i = 0; i < chars.length; i++) atlasCtx.fillText(chars[i], i * fontSize + halfFs, 0); atlasCtx.fillStyle = "rgb(255, 255, 255)"; for (let i = 0; i < chars.length; i++) atlasCtx.fillText(chars[i], i * fontSize + halfFs, fontSize); } function getGlitchColor(type) { let r, g, b; if (isVpnOn && isDarkMode) { r = 139; g = 61; b = 255; } else if (!isVpnOn && isDarkMode) { r = 0; g = 229; b = 242; } else if (isVpnOn && !isDarkMode) { r = 109; g = 40; b = 217; } else { r = 2; g = 132; b = 199; } if (type === 0) return isDarkMode ? "rgb(10, 10, 12)" : "rgb(250, 250, 252)"; if (type === 1) return `rgba(${r},${g},${b},0.9)`; if (type === 2) return `rgba(${255 - r},${255 - g},${255 - b},0.7)`; if (type === 3) return "rgba(255,255,255,0.95)"; return "rgba(255,20,80,0.95)"; } self.onmessage = async (e) => { const { type, payload } = e.data; if (type === "INIT") { ctx = payload.canvas.getContext("2d", { alpha: false }); width = payload.width; height = payload.height; isMobile = payload.isMobile; isVpnOn = payload.isVpnOn; isDarkMode = payload.isDarkMode; fontSize = payload.fontSize || 16; wasm = await init("./matrix_engine_bg.wasm"); engine = new MatrixEngine(width, height, fontSize); engine.set_mobile(isMobile); buildAtlas(); requestAnimationFrame(render); } // ДОБАВЛЯЕМ ПРОВЕРКУ: if (!engine) return; во всех остальных случаях else if (type === "RESIZE") { if (!engine) return; width = payload.width; height = payload.height; fontSize = payload.fontSize || 16; ctx.canvas.width = width; ctx.canvas.height = height; engine.set_mobile(payload.isMobile); engine.resize(width, height); buildAtlas(); } else if (type === "THEME") { isVpnOn = payload.isVpnOn; isDarkMode = payload.isDarkMode; buildAtlas(); if (ctx) { ctx.globalAlpha = 1.0; ctx.fillStyle = isDarkMode ? "rgb(10, 10, 12)" : "rgb(250, 250, 252)"; ctx.fillRect(0, 0, width, height); } } else if (type === "MOUSE_MOVE") { if (engine) engine.update_mouse(payload.x, payload.y); } else if (type === "DRAW_START") { if (engine) { engine.set_drawing(true); engine.trigger_glitch(payload.x, payload.y); } } else if (type === "DRAW_END") { if (engine) engine.set_drawing(false); } }; function render() { engine.tick(); // 1. Фон ctx.globalAlpha = 1.0; ctx.fillStyle = isDarkMode ? "rgba(10, 10, 12, 0.2)" : "rgba(250, 250, 252, 0.2)"; ctx.fillRect(0, 0, width, height); // 2. Читаем память Wasm и рисуем Задний план const bgLen = engine.bg_len(); if (bgLen > 0) { const bgData = new Float32Array(wasm.memory.buffer, engine.bg_ptr(), bgLen); const bgF = fontSize * 0.6; for (let i = 0; i < bgLen; i += 4) { ctx.globalAlpha = bgData[i + 3]; ctx.drawImage( atlasCanvas, bgData[i + 2] * fontSize, 0, fontSize, fontSize, bgData[i], bgData[i + 1], bgF, bgF, ); } } // 3. Читаем память Wasm и рисуем Передний план const fgLen = engine.fg_len(); const fgData = new Float32Array(wasm.memory.buffer, engine.fg_ptr(), fgLen); for (let i = 0; i < fgLen; i += 5) { ctx.globalAlpha = fgData[i + 4]; ctx.drawImage( atlasCanvas, fgData[i + 2] * fontSize, fgData[i + 3] * fontSize, fontSize, fontSize, fgData[i], fgData[i + 1], fontSize, fontSize, ); } // 4. Глитчи const glLen = engine.glitch_len(); if (glLen > 0) { ctx.globalAlpha = 1.0; const glData = new Float32Array( wasm.memory.buffer, engine.glitch_ptr(), glLen, ); for (let i = 0; i < glLen; i += 5) { ctx.fillStyle = getGlitchColor(glData[i + 4]); ctx.fillRect(glData[i], glData[i + 1], glData[i + 2], glData[i + 3]); } } // 5. След мыши const gsLen = engine.glass_len(); if (gsLen > 0) { const gsData = new Float32Array( wasm.memory.buffer, engine.glass_ptr(), gsLen, ); for (let i = 0; i < gsLen; i += 4) { ctx.globalAlpha = gsData[i + 3]; ctx.drawImage( atlasCanvas, gsData[i + 2] * fontSize, 0, fontSize, fontSize, gsData[i], gsData[i + 1], fontSize, fontSize, ); } } requestAnimationFrame(render); }