log error

This commit is contained in:
2026-04-24 13:16:52 +07:00
parent 942b5c56e2
commit 1d5e8389d0
6 changed files with 203 additions and 78 deletions
+106 -77
View File
@@ -12,56 +12,26 @@ import { useTheme } from "next-themes";
function CyberEye({ function CyberEye({
isClosed, isClosed,
vpnOn, vpnOn,
targetX, gaze,
targetY,
}: { }: {
isClosed: boolean; isClosed: boolean;
vpnOn: boolean; vpnOn: boolean;
targetX: number; gaze: { x: number; y: number; dir: string };
targetY: number;
}) { }) {
const eyeRef = useRef<HTMLDivElement>(null);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const [direction, setDirection] = useState("center");
// Теперь глаз только вычисляет углы на основе переданных ему координат
useEffect(() => {
if (isClosed || !eyeRef.current) return;
const rect = eyeRef.current.getBoundingClientRect();
const eyeCenterX = rect.left + rect.width / 2;
const eyeCenterY = rect.top + rect.height / 2;
const dx = targetX - eyeCenterX;
const dy = targetY - eyeCenterY;
if (Math.abs(dx) < 30 && Math.abs(dy) < 30) {
setDirection("center");
} else if (Math.abs(dx) > Math.abs(dy)) {
setDirection(dx > 0 ? "right" : "left");
} else {
setDirection(dy > 0 ? "down" : "up");
}
const angle = Math.atan2(dy, dx);
const maxRadius = 22;
// Чуть усилили множитель (0.15), чтобы глаза были более "отзывчивыми"
const distance = Math.min(maxRadius, Math.hypot(dx, dy) * 0.15);
setOffset({ x: Math.cos(angle) * distance, y: Math.sin(angle) * distance });
}, [targetX, targetY, isClosed]);
const eyeVariants: Variants = { const eyeVariants: Variants = {
open: { open: {
height: 72, height: 72,
width: 120, width: 120,
borderRadius: 36, borderRadius: 36,
borderWidth: 4, borderWidth: 4,
transition: { type: "spring", stiffness: 450, damping: 12 }, transition: { type: "spring", stiffness: 400, damping: 15 },
}, },
closed: { closed: {
height: 6, height: 6,
width: 140, width: 140,
borderRadius: 8, borderRadius: 8,
borderWidth: 3, borderWidth: 3,
transition: { type: "spring", stiffness: 600, damping: 14 }, transition: { type: "spring", stiffness: 500, damping: 15 },
}, },
}; };
@@ -71,7 +41,6 @@ function CyberEye({
return ( return (
<motion.div <motion.div
ref={eyeRef}
variants={eyeVariants} variants={eyeVariants}
initial="open" initial="open"
animate={isClosed ? "closed" : "open"} animate={isClosed ? "closed" : "open"}
@@ -79,52 +48,78 @@ function CyberEye({
> >
<motion.div <motion.div
animate={{ animate={{
x: isClosed ? 0 : offset.x, x: isClosed ? 0 : gaze.x,
y: isClosed ? 0 : offset.y, y: isClosed ? 0 : gaze.y,
scale: isClosed ? 0.3 : 1, scale: isClosed ? 0.3 : 1,
opacity: isClosed ? 0 : 1, opacity: isClosed ? 0 : 1,
}} }}
transition={{ type: "tween", ease: "easeOut", duration: 0.1 }} // Физика саккады: резкий прыжок с легкой микро-упругостью в конце
transition={{ type: "spring", stiffness: 700, damping: 25, mass: 0.5 }}
className="relative flex items-center justify-center text-foreground pointer-events-none" className="relative flex items-center justify-center text-foreground pointer-events-none"
> >
<span <span
className={`absolute -top-4 text-xs transition-opacity ${direction === "up" ? "opacity-100 scale-125" : "opacity-20"}`} className={`absolute -top-4 text-xs transition-all duration-300 ${
gaze.dir === "up"
? "opacity-100 scale-125 drop-shadow-md"
: "opacity-20"
}`}
> >
</span> </span>
<span <span
className={`absolute -bottom-4 text-xs transition-opacity ${direction === "down" ? "opacity-100 scale-125" : "opacity-20"}`} className={`absolute -bottom-4 text-xs transition-all duration-300 ${
gaze.dir === "down"
? "opacity-100 scale-125 drop-shadow-md"
: "opacity-20"
}`}
> >
</span> </span>
<span <span
className={`absolute -left-5 text-xs transition-opacity ${direction === "left" ? "opacity-100 scale-125" : "opacity-20"}`} className={`absolute -left-5 text-xs transition-all duration-300 ${
gaze.dir === "left"
? "opacity-100 scale-125 drop-shadow-md"
: "opacity-20"
}`}
> >
</span> </span>
<span <span
className={`absolute -right-5 text-xs transition-opacity ${direction === "right" ? "opacity-100 scale-125" : "opacity-20"}`} className={`absolute -right-5 text-xs transition-all duration-300 ${
gaze.dir === "right"
? "opacity-100 scale-125 drop-shadow-md"
: "opacity-20"
}`}
> >
</span> </span>
<span className="text-3xl z-10 drop-shadow-md"></span> <span className="text-3xl z-10 drop-shadow-md"></span>
</motion.div> </motion.div>
{/* Лазерная полоса при закрытом глазе */}
<motion.div <motion.div
className={`absolute h-0.5 w-full ${vpnOn ? "bg-purple-600 dark:bg-primary" : "bg-cyan-600 dark:bg-secondary"}`} className={`absolute h-0.5 w-full ${
vpnOn
? "bg-purple-600 dark:bg-primary shadow-[0_0_8px_var(--primary)]"
: "bg-cyan-600 dark:bg-secondary"
}`}
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: isClosed ? 1 : 0 }} animate={{ opacity: isClosed ? 1 : 0 }}
transition={{ duration: 0.2 }}
/> />
</motion.div> </motion.div>
); );
} }
function CyberWatcherEyes({ vpnOn }: { vpnOn: boolean }) { function CyberWatcherEyes({ vpnOn }: { vpnOn: boolean }) {
const wrapperRef = useRef<HTMLDivElement>(null);
const [isBlinking, setIsBlinking] = useState(false); const [isBlinking, setIsBlinking] = useState(false);
const [targetCoords, setTargetCoords] = useState({ x: 0, y: 0 }); const [gaze, setGaze] = useState({ x: 0, y: 0, dir: "center" });
const timerRef = useRef<any>(null); const timerRef = useRef<any>(null);
const lastInteraction = useRef<number>(0); const lastInteraction = useRef<number>(0);
// Логика моргания // Классическое случайное моргание
useEffect(() => { useEffect(() => {
const schedule = () => { const schedule = () => {
timerRef.current = setTimeout( timerRef.current = setTimeout(
@@ -133,19 +128,48 @@ function CyberWatcherEyes({ vpnOn }: { vpnOn: boolean }) {
setTimeout(() => { setTimeout(() => {
setIsBlinking(false); setIsBlinking(false);
schedule(); schedule();
}, 140); }, 120); // Чуть ускорили моргание
}, },
Math.random() * 3500 + 1500, Math.random() * 4000 + 1000,
); );
}; };
if (!vpnOn) schedule(); if (!vpnOn) schedule();
return () => clearTimeout(timerRef.current); return () => clearTimeout(timerRef.current);
}, [vpnOn]); }, [vpnOn]);
// Логика слежения и случайного блуждания // Единая логика взгляда (Unified Gaze Vector)
useEffect(() => { useEffect(() => {
// Изначально смотрим в центр const updateGaze = (targetX: number, targetY: number) => {
setTargetCoords({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); if (!wrapperRef.current) return;
// Вычисляем центр относительно "переносицы" (контейнера), а не каждого глаза
const rect = wrapperRef.current.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const dx = targetX - centerX;
const dy = targetY - centerY;
let dir = "center";
// Увеличили мертвую зону, чтобы взгляд чаще казался "сосредоточенным"
if (Math.abs(dx) < 60 && Math.abs(dy) < 60) {
dir = "center";
} else if (Math.abs(dx) > Math.abs(dy)) {
dir = dx > 0 ? "right" : "left";
} else {
dir = dy > 0 ? "down" : "up";
}
const angle = Math.atan2(dy, dx);
const maxRadius = 20;
// Небольшой коэффициент, чтобы глаза не бились о края орбиты слишком быстро
const distance = Math.min(maxRadius, Math.hypot(dx, dy) * 0.1);
setGaze({
x: Math.cos(angle) * distance,
y: Math.sin(angle) * distance,
dir,
});
};
const handleInteract = (e: MouseEvent | TouchEvent) => { const handleInteract = (e: MouseEvent | TouchEvent) => {
lastInteraction.current = Date.now(); lastInteraction.current = Date.now();
@@ -158,30 +182,38 @@ function CyberWatcherEyes({ vpnOn }: { vpnOn: boolean }) {
clientX = (e as MouseEvent).clientX; clientX = (e as MouseEvent).clientX;
clientY = (e as MouseEvent).clientY; clientY = (e as MouseEvent).clientY;
} }
updateGaze(clientX, clientY);
setTargetCoords({ x: clientX, y: clientY });
}; };
window.addEventListener("mousemove", handleInteract); window.addEventListener("mousemove", handleInteract);
window.addEventListener("touchstart", handleInteract, { passive: true }); window.addEventListener("touchstart", handleInteract, { passive: true });
window.addEventListener("pointerdown", handleInteract); window.addEventListener("pointerdown", handleInteract);
// Интервал для "живого" взгляда на мобилках // Логика "живого" блуждания для мобилок
const randomLookInterval = setInterval(() => { const randomLookInterval = setInterval(() => {
const isMobile = window.innerWidth <= 768 || "ontouchstart" in window; const isMobile = window.innerWidth <= 768 || "ontouchstart" in window;
// Если на мобилке и юзер не трогал экран последние 2.5 секунды
if (isMobile && Date.now() - lastInteraction.current > 2500) { if (isMobile && Date.now() - lastInteraction.current > 2500) {
// Выбираем случайную точку, но не слишком близко к краям экрана if (Math.random() > 0.2) {
setTargetCoords({ // Выбираем новую точку
x: const rx =
window.innerWidth * 0.2 + Math.random() * (window.innerWidth * 0.6), window.innerWidth * 0.1 + Math.random() * (window.innerWidth * 0.8);
y: const ry =
window.innerHeight * 0.2 + window.innerHeight * 0.1 +
Math.random() * (window.innerHeight * 0.6), Math.random() * (window.innerHeight * 0.8);
}); updateGaze(rx, ry);
// Микро-моргание при скачке взгляда (очень органичный эффект)
if (Math.random() > 0.6) {
setIsBlinking(true);
setTimeout(() => setIsBlinking(false), 80);
} }
}, 2000); // Раз в 2 секунды глаза могут сменить цель } else {
// Иногда смотрим прямо
updateGaze(window.innerWidth / 2, window.innerHeight / 2);
}
}
}, 1800); // Чуть чаще меняем взгляд на телефоне
return () => { return () => {
window.removeEventListener("mousemove", handleInteract); window.removeEventListener("mousemove", handleInteract);
@@ -194,19 +226,13 @@ function CyberWatcherEyes({ vpnOn }: { vpnOn: boolean }) {
const shouldBeClosed = vpnOn || isBlinking; const shouldBeClosed = vpnOn || isBlinking;
return ( return (
<div className="flex gap-6 md:gap-10 justify-center mb-12 h-20 items-center select-none pointer-events-none"> <div
<CyberEye ref={wrapperRef}
isClosed={shouldBeClosed} className="flex gap-6 md:gap-10 justify-center mb-12 h-20 items-center select-none pointer-events-none"
vpnOn={vpnOn} >
targetX={targetCoords.x} {/* Теперь оба глаза получают абсолютно одинаковое смещение */}
targetY={targetCoords.y} <CyberEye isClosed={shouldBeClosed} vpnOn={vpnOn} gaze={gaze} />
/> <CyberEye isClosed={shouldBeClosed} vpnOn={vpnOn} gaze={gaze} />
<CyberEye
isClosed={shouldBeClosed}
vpnOn={vpnOn}
targetX={targetCoords.x}
targetY={targetCoords.y}
/>
</div> </div>
); );
} }
@@ -253,6 +279,7 @@ export function Hero({
<span>{isVpnOn ? dict.badgeSecure : dict.badge}</span> <span>{isVpnOn ? dict.badgeSecure : dict.badge}</span>
</motion.div> </motion.div>
{/* НАШИ ИДЕАЛЬНЫЕ ГЛАЗА */}
<CyberWatcherEyes vpnOn={isVpnOn} /> <CyberWatcherEyes vpnOn={isVpnOn} />
<h1 className="text-4xl md:text-6xl font-extrabold tracking-tight text-foreground mb-6 font-mono cursor-default"> <h1 className="text-4xl md:text-6xl font-extrabold tracking-tight text-foreground mb-6 font-mono cursor-default">
@@ -280,7 +307,9 @@ export function Hero({
className="data-[state=checked]:bg-primary data-[state=unchecked]:bg-secondary" className="data-[state=checked]:bg-primary data-[state=unchecked]:bg-secondary"
/> />
<span <span
className={`text-base font-bold font-mono transition-colors ${isVpnOn ? "text-primary" : "text-foreground"}`} className={`text-base font-bold font-mono transition-colors ${
isVpnOn ? "text-primary" : "text-foreground"
}`}
> >
Netrunner VPN Netrunner VPN
</span> </span>
+11
View File
@@ -14,6 +14,16 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.17" version = "0.2.17"
@@ -47,6 +57,7 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
name = "matrix-engine" name = "matrix-engine"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"getrandom", "getrandom",
"js-sys", "js-sys",
"rand", "rand",
+1
View File
@@ -19,3 +19,4 @@ web-sys = { version = "0.3", features = [
] } ] }
rand = "0.8" rand = "0.8"
getrandom = { version = "0.2", features = ["js"] } getrandom = { version = "0.2", features = ["js"] }
console_error_panic_hook = "0.1.7"
+1
View File
@@ -85,6 +85,7 @@ pub struct MatrixEngine {
impl MatrixEngine { impl MatrixEngine {
#[wasm_bindgen(constructor)] #[wasm_bindgen(constructor)]
pub fn new(width: u32, height: u32, font_size: u32) -> MatrixEngine { pub fn new(width: u32, height: u32, font_size: u32) -> MatrixEngine {
console_error_panic_hook::set_once();
let charset_len = 84; // Длина нашего алфавита let charset_len = 84; // Длина нашего алфавита
let mut engine = MatrixEngine { let mut engine = MatrixEngine {
width, width,
+83
View File
@@ -147,6 +147,17 @@ function __wbg_get_imports() {
const ret = arg0.crypto; const ret = arg0.crypto;
return ret; return ret;
}, },
__wbg_error_a6fa202b58aa1cd3: function(arg0, arg1) {
let deferred0_0;
let deferred0_1;
try {
deferred0_0 = arg0;
deferred0_1 = arg1;
console.error(getStringFromWasm0(arg0, arg1));
} finally {
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
}
},
__wbg_getRandomValues_c44a50d8cfdaebeb: function() { return handleError(function (arg0, arg1) { __wbg_getRandomValues_c44a50d8cfdaebeb: function() { return handleError(function (arg0, arg1) {
arg0.getRandomValues(arg1); arg0.getRandomValues(arg1);
}, arguments); }, }, arguments); },
@@ -158,6 +169,10 @@ function __wbg_get_imports() {
const ret = arg0.msCrypto; const ret = arg0.msCrypto;
return ret; return ret;
}, },
__wbg_new_227d7c05414eb861: function() {
const ret = new Error();
return ret;
},
__wbg_new_with_length_8c854e41ea4dae9b: function(arg0) { __wbg_new_with_length_8c854e41ea4dae9b: function(arg0) {
const ret = new Uint8Array(arg0 >>> 0); const ret = new Uint8Array(arg0 >>> 0);
return ret; return ret;
@@ -180,6 +195,13 @@ function __wbg_get_imports() {
const ret = module.require; const ret = module.require;
return ret; return ret;
}, arguments); }, }, arguments); },
__wbg_stack_3b0d974bbf31e44f: function(arg0, arg1) {
const ret = arg1.stack;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
},
__wbg_static_accessor_GLOBAL_8cfadc87a297ca02: function() { __wbg_static_accessor_GLOBAL_8cfadc87a297ca02: function() {
const ret = typeof global === 'undefined' ? null : global; const ret = typeof global === 'undefined' ? null : global;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
@@ -245,6 +267,14 @@ function getArrayU8FromWasm0(ptr, len) {
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
} }
let cachedDataViewMemory0 = null;
function getDataViewMemory0() {
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
}
return cachedDataViewMemory0;
}
function getStringFromWasm0(ptr, len) { function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0; ptr = ptr >>> 0;
return decodeText(ptr, len); return decodeText(ptr, len);
@@ -271,6 +301,43 @@ function isLikeNone(x) {
return x === undefined || x === null; return x === undefined || x === null;
} }
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length, 1) >>> 0;
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len, 1) >>> 0;
const mem = getUint8ArrayMemory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
const ret = cachedTextEncoder.encodeInto(arg, view);
offset += ret.written;
ptr = realloc(ptr, len, offset, 1) >>> 0;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode(); cachedTextDecoder.decode();
const MAX_SAFARI_DECODE_BYTES = 2146435072; const MAX_SAFARI_DECODE_BYTES = 2146435072;
@@ -285,10 +352,26 @@ function decodeText(ptr, len) {
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
} }
const cachedTextEncoder = new TextEncoder();
if (!('encodeInto' in cachedTextEncoder)) {
cachedTextEncoder.encodeInto = function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
};
}
let WASM_VECTOR_LEN = 0;
let wasmModule, wasm; let wasmModule, wasm;
function __wbg_finalize_init(instance, module) { function __wbg_finalize_init(instance, module) {
wasm = instance.exports; wasm = instance.exports;
wasmModule = module; wasmModule = module;
cachedDataViewMemory0 = null;
cachedUint8ArrayMemory0 = null; cachedUint8ArrayMemory0 = null;
wasm.__wbindgen_start(); wasm.__wbindgen_start();
return wasm; return wasm;
Binary file not shown.