"use client"; import React, { useRef, useEffect, useState } from "react"; import { useTheme } from "next-themes"; interface NetrunnerMatrixProps { isSecure?: boolean; } export const NetrunnerMatrix: React.FC = ({ isSecure = false, }) => { const containerRef = useRef(null); const canvasRef = useRef(null); const workerRef = useRef(null); const [isLoaded, setIsLoaded] = useState(false); const hasTransferred = useRef(false); // --- БЕЗОПАСНОЕ ЧТЕНИЕ ТЕМЫ NEXT.JS --- const { resolvedTheme, theme } = useTheme(); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); // Вычисляем актуальную тему const isDarkMode = mounted ? resolvedTheme === "dark" || theme === "dark" : true; const checkIsMobile = () => { if (typeof window === "undefined") return false; return ( window.innerWidth <= 768 || "ontouchstart" in window || navigator.maxTouchPoints > 0 ); }; useEffect(() => { if (typeof window === "undefined" || !("OffscreenCanvas" in window)) return; const canvas = canvasRef.current; const container = containerRef.current; if (!canvas || !container || hasTransferred.current) return; try { const isMobile = checkIsMobile(); // Используем 1.0 для четкости на мобилках, как ты и хотел const pixelScale = isMobile ? 1.0 : Math.min(window.devicePixelRatio || 1, 1.5); const width = container.clientWidth * pixelScale; const height = container.clientHeight * pixelScale; canvas.width = width; canvas.height = height; const worker = new Worker("/wasm-matrix/matrix_worker.js", { type: "module", }); workerRef.current = worker; const offscreen = canvas.transferControlToOffscreen(); hasTransferred.current = true; worker.postMessage( { type: "INIT", payload: { canvas: offscreen, width, height, isMobile, isVpnOn: isSecure, isDarkMode, // Здесь передается начальная тема fontSize: isMobile ? 10 : 16, }, }, [offscreen], ); setIsLoaded(true); const handleResize = () => { if (!container || !workerRef.current) return; const isMob = checkIsMobile(); // Держим scale консистентным с INIT (1.0 для мобилок) const scale = isMob ? 1.0 : Math.min(window.devicePixelRatio || 1, 1.5); workerRef.current.postMessage({ type: "RESIZE", payload: { width: container.clientWidth * scale, height: container.clientHeight * scale, isMobile: isMob, fontSize: isMob ? 10 : 16, }, }); }; window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); worker.terminate(); }; } catch (e) { console.error("Matrix Worker Bridge Failed:", e); } }, []); // Пустой массив, так как INIT делается один раз // --- ДОБАВЛЕНО: ОБНОВЛЕНИЕ ТЕМЫ И VPN --- useEffect(() => { if (workerRef.current && mounted) { workerRef.current.postMessage({ type: "THEME", payload: { isVpnOn: isSecure, isDarkMode: isDarkMode, }, }); } }, [isSecure, isDarkMode, mounted]); // ФИКС ОШИБКИ clientX (Твой код был верным, оставляем с микро-правкой) useEffect(() => { const handleEvents = (e: any) => { if (!workerRef.current || !canvasRef.current) return; const isDown = e.type === "pointerdown" || e.type === "touchstart"; const isUp = e.type === "pointerup" || e.type === "touchend" || e.type === "pointercancel"; const type = isDown ? "DRAW_START" : isUp ? "DRAW_END" : "MOUSE_MOVE"; let clientX: number | null = null; let clientY: number | null = null; if (e.touches && e.touches.length > 0) { clientX = e.touches[0].clientX; clientY = e.touches[0].clientY; } else if (e.clientX !== undefined) { clientX = e.clientX; clientY = e.clientY; } if (clientX === null || clientY === null) { if (isUp) workerRef.current.postMessage({ type }); return; } const rect = canvasRef.current.getBoundingClientRect(); // Используем фактическую ширину буфера канваса для правильных координат в Wasm const scaleX = canvasRef.current.width / rect.width; const scaleY = canvasRef.current.height / rect.height; const x = (clientX - rect.left) * scaleX; const y = (clientY - rect.top) * scaleY; workerRef.current.postMessage({ type, payload: { x, y } }); }; const options = { passive: true }; window.addEventListener("pointerdown", handleEvents); window.addEventListener("pointermove", handleEvents); window.addEventListener("pointerup", handleEvents); window.addEventListener("pointercancel", handleEvents); window.addEventListener("touchstart", handleEvents, options); window.addEventListener("touchmove", handleEvents, options); window.addEventListener("touchend", handleEvents, options); return () => { window.removeEventListener("pointerdown", handleEvents); window.removeEventListener("pointermove", handleEvents); window.removeEventListener("pointerup", handleEvents); window.removeEventListener("pointercancel", handleEvents); window.removeEventListener("touchstart", handleEvents); window.removeEventListener("touchmove", handleEvents); window.removeEventListener("touchend", handleEvents); }; }, []); return (
); };