"use client"; import React, { useRef, useEffect, useState } from "react"; import { useTheme } from "next-themes"; type MatrixEngine = { tick: () => void; update_mouse: (x: number, y: number) => void; trigger_glitch: (x: number, y: number) => void; set_drawing: (is_drawing: boolean) => void; set_theme: (is_vpn_on: boolean, is_dark_mode: boolean) => void; resize: (width: number, height: number) => void; }; interface NetrunnerMatrixProps { isSecure?: boolean; } export const NetrunnerMatrix: React.FC = ({ isSecure = false, }) => { const containerRef = useRef(null); const canvasRef = useRef(null); const engineRef = useRef(null); const animationFrameId = useRef(null); const [wasmLoaded, setWasmLoaded] = useState(false); const { resolvedTheme } = useTheme(); const isDarkMode = resolvedTheme === "dark"; useEffect(() => { const canvas = canvasRef.current; const container = containerRef.current; if (!canvas || !container) return; const loadWasm = async () => { try { // НАДЕЖНЫЙ ПУТЬ ДЛЯ ПРОДАКШЕНА: // Форсируем абсолютные URL, чтобы Webpack не пытался их бандлить const baseUrl = window.location.origin; const jsUrl = new URL("/wasm-matrix/matrix_engine.js", baseUrl).href; const wasmUrl = new URL("/wasm-matrix/matrix_engine_bg.wasm", baseUrl) .href; const wasmModule = await import(/* webpackIgnore: true */ jsUrl); await wasmModule.default(wasmUrl); const fontSize = 16; canvas.width = container.clientWidth; canvas.height = container.clientHeight; const engine = new wasmModule.MatrixEngine(canvas, fontSize); engineRef.current = engine; engine.set_theme(isSecure, isDarkMode); setWasmLoaded(true); const render = () => { engine.tick(); if (animationFrameId.current !== null) { animationFrameId.current = requestAnimationFrame(render); } }; animationFrameId.current = requestAnimationFrame(render); } catch (e) { console.error("Wasm Engine Load Failed:", e); } }; loadWasm(); const handleResize = () => { if (canvas && container && engineRef.current) { canvas.width = container.clientWidth; canvas.height = container.clientHeight; engineRef.current.resize(canvas.width, canvas.height); } }; window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); if (animationFrameId.current !== null) { cancelAnimationFrame(animationFrameId.current); animationFrameId.current = null; } }; }, []); useEffect(() => { if (engineRef.current) { engineRef.current.set_theme(isSecure, isDarkMode); } }, [isSecure, isDarkMode]); // ГЛОБАЛЬНЫЙ ПЕРЕХВАТ (ВКЛЮЧАЯ ТАЧ-СОБЫТИЯ) useEffect(() => { const triggerAction = (clientX: number, clientY: number) => { if (!canvasRef.current || !engineRef.current) return; const rect = canvasRef.current.getBoundingClientRect(); const x = clientX - rect.left; const y = clientY - rect.top; if (y < 0 || y > rect.height) return; engineRef.current.set_drawing(true); engineRef.current.trigger_glitch(x, y); }; const moveAction = (clientX: number, clientY: number) => { if (!canvasRef.current || !engineRef.current) return; const rect = canvasRef.current.getBoundingClientRect(); engineRef.current.update_mouse(clientX - rect.left, clientY - rect.top); }; const handlePointerDown = (e: MouseEvent) => triggerAction(e.clientX, e.clientY); const handlePointerMove = (e: MouseEvent) => moveAction(e.clientX, e.clientY); const handlePointerUp = () => engineRef.current?.set_drawing(false); const handleTouchStart = (e: TouchEvent) => triggerAction(e.touches[0].clientX, e.touches[0].clientY); const handleTouchMove = (e: TouchEvent) => moveAction(e.touches[0].clientX, e.touches[0].clientY); window.addEventListener("mousedown", handlePointerDown); window.addEventListener("mousemove", handlePointerMove); window.addEventListener("mouseup", handlePointerUp); window.addEventListener("touchstart", handleTouchStart, { passive: true }); window.addEventListener("touchmove", handleTouchMove, { passive: true }); window.addEventListener("touchend", handlePointerUp); return () => { window.removeEventListener("mousedown", handlePointerDown); window.removeEventListener("mousemove", handlePointerMove); window.removeEventListener("mouseup", handlePointerUp); window.removeEventListener("touchstart", handleTouchStart); window.removeEventListener("touchmove", handleTouchMove); window.removeEventListener("touchend", handlePointerUp); }; }, []); return (
); };