"use client"; import React, { useRef, useEffect, useState, useCallback } from "react"; import { useTheme } from "next-themes"; // Версионирование для сброса кэша Cloudflare при деплое const ENGINE_VERSION = "3.0.2"; interface NetrunnerMatrixProps { isSecure?: boolean; } export const NetrunnerMatrix: React.FC = ({ isSecure = false, }) => { const containerRef = useRef(null); const workerRef = useRef(null); const [isLoaded, setIsLoaded] = useState(false); const hasTransferred = useRef(false); const { resolvedTheme, theme } = useTheme(); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); return () => setMounted(false); }, []); const isDarkMode = mounted ? resolvedTheme === "dark" || theme === "dark" : true; const checkIsMobile = useCallback(() => { if (typeof window === "undefined") return false; return window.innerWidth <= 768 || "ontouchstart" in window; }, []); const getEyeY = useCallback(() => { const anchor = document.getElementById("eye-anchor"); const container = containerRef.current; if (anchor && container) { const ar = anchor.getBoundingClientRect(); const cr = container.getBoundingClientRect(); return ar.top - cr.top + ar.height / 2; } // Фолбэк, если якорь еще не отрендерился return typeof window !== "undefined" ? window.innerHeight * 0.4 : 300; }, []); // === 1. ИНИЦИАЛИЗАЦИЯ WEBGL === useEffect(() => { if ( !mounted || typeof window === "undefined" || !("OffscreenCanvas" in window) ) return; const container = containerRef.current; if (!container || hasTransferred.current) return; const canvas = document.createElement("canvas"); canvas.className = "absolute inset-0 w-full h-full block transition-opacity duration-1000 opacity-0 z-0"; container.insertBefore(canvas, container.firstChild); try { const isMobile = checkIsMobile(); // --- ДЕТЕКТОР СЛАБОГО ЖЕЛЕЗА И ДАУНСЭМПЛИНГ --- const cores = navigator.hardwareConcurrency || 4; const memory = (navigator as any).deviceMemory || 8; const isWeakHardware = cores <= 4 || memory <= 4; const isLowQuality = isMobile || isWeakHardware; // На слабом ПК и телефонах рендерим 75% от разрешения экрана const pixelScale = 1; // ---------------------------------------------- const logicalWidth = container.clientWidth; const logicalHeight = container.clientHeight; canvas.width = logicalWidth * pixelScale; canvas.height = logicalHeight * pixelScale; const worker = new Worker( `/wasm-matrix/matrix_worker.js?v=${ENGINE_VERSION}`, { type: "module" }, ); workerRef.current = worker; const offscreen = canvas.transferControlToOffscreen(); hasTransferred.current = true; const eyeScale = isMobile ? 0.8 : 1.5; // Оставили твои размеры worker.postMessage( { type: "INIT", payload: { canvas: offscreen, logicalWidth, logicalHeight, pixelScale, eyeY: getEyeY(), eyeScale, isMobile, isVpnOn: isSecure, isDarkMode, fontSize: isMobile ? 12 : 16, isLowQuality, // Передаем флаг в воркер }, }, [offscreen], ); worker.onmessage = (e) => { if (e.data.type === "READY") { canvas.classList.remove("opacity-0"); canvas.classList.add("opacity-100"); } }; const resizeObserver = new ResizeObserver((entries) => { for (let entry of entries) { const { width, height } = entry.contentRect; if (width === 0 || height === 0) continue; const isMob = width <= 768; const lowQual = isMob || isWeakHardware; const pScale = 1; workerRef.current?.postMessage({ type: "RESIZE", payload: { logicalWidth: width, logicalHeight: height, pixelScale: pScale, eyeY: getEyeY(), eyeScale: isMob ? 0.8 : 1.5, isMobile: isMob, fontSize: isMob ? 12 : 16, isLowQuality: lowQual, }, }); } }); resizeObserver.observe(container); return () => { resizeObserver.disconnect(); if (workerRef.current) { workerRef.current.terminate(); workerRef.current = null; } if (container.contains(canvas)) { container.removeChild(canvas); } hasTransferred.current = false; }; } catch (e) { console.error("Matrix WebGL Bridge Failed:", e); } }, [mounted, checkIsMobile, getEyeY]); // === 2. РЕАКЦИЯ НА ТЕМУ И VPN (ВОЗВРАЩАЕМ!) === useEffect(() => { if (workerRef.current && mounted) { workerRef.current.postMessage({ type: "THEME", payload: { isVpnOn: isSecure, isDarkMode: isDarkMode, }, }); } }, [isSecure, isDarkMode, mounted]); // Теперь React сообщит воркеру об изменениях // === 3. ИНТЕРАКТИВНОСТЬ (МЫШЬ, КЛИКИ, ГИРОСКОП) === useEffect(() => { if (!mounted || !workerRef.current || !containerRef.current) return; const container = containerRef.current; const handleOrientation = (e: DeviceOrientationEvent) => { let gamma = e.gamma || 0; if (gamma > 45) gamma = 45; if (gamma < -45) gamma = -45; const tiltX = gamma / 45.0; workerRef.current?.postMessage({ type: "TILT", payload: { x: tiltX, y: 0 }, }); }; window.addEventListener("deviceorientation", handleOrientation); const handleEvents = (e: PointerEvent | TouchEvent) => { const isDown = e.type === "pointerdown" || e.type === "touchstart"; const isMove = e.type === "pointermove" || e.type === "touchmove"; if (e.target instanceof Element && e.target.closest("button, a, input")) return; let clientX, clientY; if ("touches" in e) { clientX = e.touches[0]?.clientX; clientY = e.touches[0]?.clientY; } else { clientX = (e as PointerEvent).clientX; clientY = (e as PointerEvent).clientY; } if (clientX === undefined || clientY === undefined) return; const rect = container.getBoundingClientRect(); const px = clientX - rect.left; const py = clientY - rect.top; if (isDown) { workerRef.current?.postMessage({ type: "SHOCKWAVE", payload: { x: px, y: py }, }); workerRef.current?.postMessage({ type: "DRAW_START", payload: { x: px, y: py }, }); } if (isMove) { workerRef.current?.postMessage({ type: "KINETIC_SWIPE", payload: { x: px, y: py }, }); workerRef.current?.postMessage({ type: "MOUSE_MOVE", payload: { x: px, y: py }, }); } }; const handlePointerUp = () => { workerRef.current?.postMessage({ type: "DRAW_END", payload: {} }); }; window.addEventListener("pointerdown", handleEvents, { passive: true }); window.addEventListener("pointermove", handleEvents, { passive: true }); window.addEventListener("pointerup", handlePointerUp, { passive: true }); window.addEventListener("touchstart", handleEvents, { passive: true }); window.addEventListener("touchmove", handleEvents, { passive: true }); window.addEventListener("touchend", handlePointerUp, { passive: true }); return () => { window.removeEventListener("deviceorientation", handleOrientation); window.removeEventListener("pointerdown", handleEvents); window.removeEventListener("pointermove", handleEvents); window.removeEventListener("pointerup", handlePointerUp); window.removeEventListener("touchstart", handleEvents); window.removeEventListener("touchmove", handleEvents); window.removeEventListener("touchend", handlePointerUp); }; }, [mounted]); return (
); };