eyes fix
This commit is contained in:
@@ -1,5 +0,0 @@
|
|||||||
<!-- BEGIN:nextjs-agent-rules -->
|
|
||||||
# This is NOT the Next.js you know
|
|
||||||
|
|
||||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
|
||||||
<!-- END:nextjs-agent-rules -->
|
|
||||||
+6
-4
@@ -5,14 +5,16 @@ cd matrix-engine
|
|||||||
# Собираем проект
|
# Собираем проект
|
||||||
wasm-pack build --target web
|
wasm-pack build --target web
|
||||||
|
|
||||||
|
# Копируем worker в pkg
|
||||||
|
cp matrix_worker.js pkg/
|
||||||
|
|
||||||
# Возвращаемся в корень
|
# Возвращаемся в корень
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
# Создаем папку, если её нет (-p не выдаст ошибку, если она уже есть)
|
# Создаем папку, если её нет
|
||||||
mkdir -p public/wasm-matrix
|
mkdir -p public/wasm-matrix
|
||||||
|
|
||||||
# Копируем конкретные файлы из pkg в public
|
# Копируем все из pkg в public
|
||||||
cp matrix-engine/pkg/matrix_engine_bg.wasm public/wasm-matrix/
|
cp -r matrix-engine/pkg/* public/wasm-matrix/
|
||||||
cp matrix-engine/pkg/matrix_engine.js public/wasm-matrix/
|
|
||||||
|
|
||||||
echo "✅ Wasm Engine updated in public/wasm-matrix/"
|
echo "✅ Wasm Engine updated in public/wasm-matrix/"
|
||||||
@@ -47,6 +47,18 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
|
|||||||
|
|
||||||
if (!canvas || !container || hasTransferred.current) return;
|
if (!canvas || !container || hasTransferred.current) return;
|
||||||
|
|
||||||
|
|
||||||
|
const getEyeY = (scale) => {
|
||||||
|
const anchor = document.getElementById("eye-anchor");
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (anchor && canvas) {
|
||||||
|
const ar = anchor.getBoundingClientRect();
|
||||||
|
const cr = canvas.getBoundingClientRect();
|
||||||
|
return (ar.top - cr.top + ar.height / 2) * scale;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
const isMobile = checkIsMobile();
|
const isMobile = checkIsMobile();
|
||||||
const pixelScale = isMobile
|
const pixelScale = isMobile
|
||||||
? 1.0
|
? 1.0
|
||||||
@@ -75,6 +87,7 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
|
|||||||
canvas: offscreen,
|
canvas: offscreen,
|
||||||
width: initialWidth,
|
width: initialWidth,
|
||||||
height: initialHeight,
|
height: initialHeight,
|
||||||
|
eyeY: getEyeY(pixelScale),
|
||||||
isMobile,
|
isMobile,
|
||||||
isVpnOn: isSecure,
|
isVpnOn: isSecure,
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
@@ -101,6 +114,7 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
|
|||||||
payload: {
|
payload: {
|
||||||
width: w,
|
width: w,
|
||||||
height: h,
|
height: h,
|
||||||
|
eyeY: getEyeY(scale),
|
||||||
isMobile: isMob,
|
isMobile: isMob,
|
||||||
fontSize: isMob ? 10 : 16,
|
fontSize: isMob ? 10 : 16,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,241 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useRef, useEffect } from "react";
|
import { useState } from "react";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { motion, Variants } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { Shield, EyeOff } from "lucide-react";
|
import { Shield, EyeOff } from "lucide-react";
|
||||||
import { Dictionary } from "@/lib/IDict";
|
import { Dictionary } from "@/lib/IDict";
|
||||||
import { CyberButton } from "../ui/cyber-button";
|
import { CyberButton } from "../ui/cyber-button";
|
||||||
import { NetrunnerMatrix } from "../graphics/NetrunnerMatrix";
|
import { NetrunnerMatrix } from "../graphics/NetrunnerMatrix";
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
|
|
||||||
function CyberEye({
|
|
||||||
isClosed,
|
|
||||||
vpnOn,
|
|
||||||
gaze,
|
|
||||||
}: {
|
|
||||||
isClosed: boolean;
|
|
||||||
vpnOn: boolean;
|
|
||||||
gaze: { x: number; y: number; dir: string };
|
|
||||||
}) {
|
|
||||||
const eyeVariants: Variants = {
|
|
||||||
open: {
|
|
||||||
height: 72,
|
|
||||||
width: 120,
|
|
||||||
borderRadius: 36,
|
|
||||||
borderWidth: 4,
|
|
||||||
transition: { type: "spring", stiffness: 400, damping: 15 },
|
|
||||||
},
|
|
||||||
closed: {
|
|
||||||
height: 6,
|
|
||||||
width: 140,
|
|
||||||
borderRadius: 8,
|
|
||||||
borderWidth: 3,
|
|
||||||
transition: { type: "spring", stiffness: 500, damping: 15 },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const borderColor = vpnOn
|
|
||||||
? "border-purple-600 dark:border-primary shadow-[0_0_15px_rgba(139,61,255,0.3)]"
|
|
||||||
: "border-cyan-600 dark:border-secondary shadow-[0_0_15px_rgba(0,229,242,0.3)]";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
variants={eyeVariants}
|
|
||||||
initial="open"
|
|
||||||
animate={isClosed ? "closed" : "open"}
|
|
||||||
className={`relative flex items-center justify-center overflow-hidden bg-background ${borderColor} z-10 select-none`}
|
|
||||||
>
|
|
||||||
<motion.div
|
|
||||||
animate={{
|
|
||||||
x: isClosed ? 0 : gaze.x,
|
|
||||||
y: isClosed ? 0 : gaze.y,
|
|
||||||
scale: isClosed ? 0.3 : 1,
|
|
||||||
opacity: isClosed ? 0 : 1,
|
|
||||||
}}
|
|
||||||
// Физика саккады: резкий прыжок с легкой микро-упругостью в конце
|
|
||||||
transition={{ type: "spring", stiffness: 700, damping: 25, mass: 0.5 }}
|
|
||||||
className="relative flex items-center justify-center text-foreground pointer-events-none"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`absolute -top-4 text-xs transition-all duration-300 ${
|
|
||||||
gaze.dir === "up"
|
|
||||||
? "opacity-100 scale-125 drop-shadow-md"
|
|
||||||
: "opacity-20"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
▴
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`absolute -bottom-4 text-xs transition-all duration-300 ${
|
|
||||||
gaze.dir === "down"
|
|
||||||
? "opacity-100 scale-125 drop-shadow-md"
|
|
||||||
: "opacity-20"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
▾
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`absolute -left-5 text-xs transition-all duration-300 ${
|
|
||||||
gaze.dir === "left"
|
|
||||||
? "opacity-100 scale-125 drop-shadow-md"
|
|
||||||
: "opacity-20"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
◂
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`absolute -right-5 text-xs transition-all duration-300 ${
|
|
||||||
gaze.dir === "right"
|
|
||||||
? "opacity-100 scale-125 drop-shadow-md"
|
|
||||||
: "opacity-20"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
▸
|
|
||||||
</span>
|
|
||||||
<span className="text-3xl z-10 drop-shadow-md">◈</span>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Лазерная полоса при закрытом глазе */}
|
|
||||||
<motion.div
|
|
||||||
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 }}
|
|
||||||
animate={{ opacity: isClosed ? 1 : 0 }}
|
|
||||||
transition={{ duration: 0.2 }}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CyberWatcherEyes({ vpnOn }: { vpnOn: boolean }) {
|
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [isBlinking, setIsBlinking] = useState(false);
|
|
||||||
const [gaze, setGaze] = useState({ x: 0, y: 0, dir: "center" });
|
|
||||||
|
|
||||||
const timerRef = useRef<any>(null);
|
|
||||||
const lastInteraction = useRef<number>(0);
|
|
||||||
|
|
||||||
// Классическое случайное моргание
|
|
||||||
useEffect(() => {
|
|
||||||
const schedule = () => {
|
|
||||||
timerRef.current = setTimeout(
|
|
||||||
() => {
|
|
||||||
setIsBlinking(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsBlinking(false);
|
|
||||||
schedule();
|
|
||||||
}, 120); // Чуть ускорили моргание
|
|
||||||
},
|
|
||||||
Math.random() * 4000 + 1000,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
if (!vpnOn) schedule();
|
|
||||||
return () => clearTimeout(timerRef.current);
|
|
||||||
}, [vpnOn]);
|
|
||||||
|
|
||||||
// Единая логика взгляда (Unified Gaze Vector)
|
|
||||||
useEffect(() => {
|
|
||||||
const updateGaze = (targetX: number, targetY: number) => {
|
|
||||||
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) => {
|
|
||||||
lastInteraction.current = Date.now();
|
|
||||||
let clientX, clientY;
|
|
||||||
|
|
||||||
if ("touches" in e && e.touches.length > 0) {
|
|
||||||
clientX = e.touches[0].clientX;
|
|
||||||
clientY = e.touches[0].clientY;
|
|
||||||
} else {
|
|
||||||
clientX = (e as MouseEvent).clientX;
|
|
||||||
clientY = (e as MouseEvent).clientY;
|
|
||||||
}
|
|
||||||
updateGaze(clientX, clientY);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("mousemove", handleInteract);
|
|
||||||
window.addEventListener("touchstart", handleInteract, { passive: true });
|
|
||||||
window.addEventListener("pointerdown", handleInteract);
|
|
||||||
|
|
||||||
// Логика "живого" блуждания для мобилок
|
|
||||||
const randomLookInterval = setInterval(() => {
|
|
||||||
const isMobile = window.innerWidth <= 768 || "ontouchstart" in window;
|
|
||||||
|
|
||||||
if (isMobile && Date.now() - lastInteraction.current > 2500) {
|
|
||||||
if (Math.random() > 0.2) {
|
|
||||||
// Выбираем новую точку
|
|
||||||
const rx =
|
|
||||||
window.innerWidth * 0.1 + Math.random() * (window.innerWidth * 0.8);
|
|
||||||
const ry =
|
|
||||||
window.innerHeight * 0.1 +
|
|
||||||
Math.random() * (window.innerHeight * 0.8);
|
|
||||||
updateGaze(rx, ry);
|
|
||||||
|
|
||||||
// Микро-моргание при скачке взгляда (очень органичный эффект)
|
|
||||||
if (Math.random() > 0.6) {
|
|
||||||
setIsBlinking(true);
|
|
||||||
setTimeout(() => setIsBlinking(false), 80);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Иногда смотрим прямо
|
|
||||||
updateGaze(window.innerWidth / 2, window.innerHeight / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 1800); // Чуть чаще меняем взгляд на телефоне
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("mousemove", handleInteract);
|
|
||||||
window.removeEventListener("touchstart", handleInteract);
|
|
||||||
window.removeEventListener("pointerdown", handleInteract);
|
|
||||||
clearInterval(randomLookInterval);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const shouldBeClosed = vpnOn || isBlinking;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={wrapperRef}
|
|
||||||
className="flex gap-6 md:gap-10 justify-center mb-12 h-20 items-center select-none pointer-events-none"
|
|
||||||
>
|
|
||||||
{/* Теперь оба глаза получают абсолютно одинаковое смещение */}
|
|
||||||
<CyberEye isClosed={shouldBeClosed} vpnOn={vpnOn} gaze={gaze} />
|
|
||||||
<CyberEye isClosed={shouldBeClosed} vpnOn={vpnOn} gaze={gaze} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Hero({
|
export function Hero({
|
||||||
dict,
|
dict,
|
||||||
@@ -266,7 +37,7 @@ export function Hero({
|
|||||||
{/* Интерактивные элементы перехватывают клик обратно (pointer-events-auto) */}
|
{/* Интерактивные элементы перехватывают клик обратно (pointer-events-auto) */}
|
||||||
<motion.div
|
<motion.div
|
||||||
layout
|
layout
|
||||||
className={`inline-flex items-center gap-2 px-4 py-1.5 rounded-full border text-sm font-bold mb-8 transition-colors duration-500 shadow-sm cursor-default pointer-events-auto ${
|
className={`inline-flex items-center gap-2 px-4 py-1.5 rounded-full border text-sm font-bold transition-colors duration-500 shadow-sm cursor-default pointer-events-auto ${
|
||||||
isVpnOn
|
isVpnOn
|
||||||
? "bg-primary/10 border-primary/50 text-primary"
|
? "bg-primary/10 border-primary/50 text-primary"
|
||||||
: "bg-secondary/10 border-secondary/50 text-secondary"
|
: "bg-secondary/10 border-secondary/50 text-secondary"
|
||||||
@@ -280,7 +51,8 @@ export function Hero({
|
|||||||
<span>{isVpnOn ? dict.badgeSecure : dict.badge}</span>
|
<span>{isVpnOn ? dict.badgeSecure : dict.badge}</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<CyberWatcherEyes vpnOn={isVpnOn} />
|
{/* Якорь для глаз */}
|
||||||
|
<div id="eye-anchor" className="h-28 md:h-40 w-full flex-none mt-6 mb-4" />
|
||||||
|
|
||||||
<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">
|
||||||
{dict.titleStart}{" "}
|
{dict.titleStart}{" "}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export function CyberButton({
|
|||||||
!disabled && [
|
!disabled && [
|
||||||
isPrimary
|
isPrimary
|
||||||
? "bg-[#8B3DFF] border-[#8B3DFF] text-white shadow-lg shadow-purple-500/20 dark:bg-[radial-gradient(circle_at_center,_rgba(139,61,255,0.4)_0%,_rgba(139,61,255,0.1)_100%)] dark:border-[#8B3DFF]/50 dark:shadow-[0_0_25px_rgba(139,61,255,0.3)]"
|
? "bg-[#8B3DFF] border-[#8B3DFF] text-white shadow-lg shadow-purple-500/20 dark:bg-[radial-gradient(circle_at_center,_rgba(139,61,255,0.4)_0%,_rgba(139,61,255,0.1)_100%)] dark:border-[#8B3DFF]/50 dark:shadow-[0_0_25px_rgba(139,61,255,0.3)]"
|
||||||
: "text-[#00E5F2] border-[#00E5F2]/40 backdrop-blur-md bg-slate-900/5 border-[#00E5F2]/50 dark:bg-[#00E5F2]/5",
|
: "text-[#0284c7] dark:text-[#00E5F2] border-[#0284c7]/40 dark:border-[#00E5F2]/40 backdrop-blur-md bg-slate-900/5 dark:bg-[#00E5F2]/5",
|
||||||
],
|
],
|
||||||
disabled && "cursor-not-allowed opacity-50 border-dashed",
|
disabled && "cursor-not-allowed opacity-50 border-dashed",
|
||||||
className,
|
className,
|
||||||
|
|||||||
@@ -0,0 +1,317 @@
|
|||||||
|
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() {
|
||||||
|
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);
|
||||||
|
engine.set_vpn_status(isVpnOn);
|
||||||
|
if (payload.eyeY) engine.set_eye_anchor(payload.eyeY);
|
||||||
|
|
||||||
|
buildAtlas();
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
} 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);
|
||||||
|
if (payload.eyeY) engine.set_eye_anchor(payload.eyeY);
|
||||||
|
buildAtlas();
|
||||||
|
} else if (type === "THEME") {
|
||||||
|
if (!engine) return;
|
||||||
|
isVpnOn = payload.isVpnOn;
|
||||||
|
isDarkMode = payload.isDarkMode;
|
||||||
|
engine.set_vpn_status(isVpnOn);
|
||||||
|
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);
|
||||||
|
} else if (type === "GET_EYE_DATA") {
|
||||||
|
if (!engine || !wasm) return;
|
||||||
|
const eyeLen = engine.eye_len();
|
||||||
|
if (eyeLen > 0) {
|
||||||
|
const eyePtr = engine.eye_ptr();
|
||||||
|
const eyeData = new Float32Array(wasm.memory.buffer, eyePtr, eyeLen);
|
||||||
|
self.postMessage({ type: "EYE_DATA", payload: eyeData.slice() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function drawEye(ctx, cx, cy, gazeX, gazeY, dir, isClosed) {
|
||||||
|
const color = isVpnOn
|
||||||
|
? (isDarkMode ? "rgb(139, 61, 255)" : "rgb(109, 40, 217)")
|
||||||
|
: (isDarkMode ? "rgb(0, 229, 242)" : "rgb(2, 132, 199)");
|
||||||
|
|
||||||
|
const bgFill = isDarkMode ? "rgb(10, 10, 12)" : "rgb(250, 250, 252)";
|
||||||
|
|
||||||
|
// Interpolate properties based on isClosed (0.0 to 1.0)
|
||||||
|
const eyeHeight = 72 * (1 - isClosed) + 6 * isClosed;
|
||||||
|
const eyeWidth = 120 * (1 - isClosed) + 140 * isClosed;
|
||||||
|
const eyeRadius = 36 * (1 - isClosed) + 8 * isClosed;
|
||||||
|
const laserWidth = 140 * isClosed;
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.fillStyle = bgFill;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.roundRect(cx - eyeWidth/2, cy - eyeHeight/2, eyeWidth, eyeHeight, eyeRadius);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.strokeStyle = color;
|
||||||
|
ctx.lineWidth = 4 * (1 - isClosed) + 3 * isClosed;
|
||||||
|
ctx.shadowColor = color;
|
||||||
|
ctx.shadowBlur = 15 * (1 - isClosed) + 8 * isClosed;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
if (isVpnOn && isClosed > 0.1) {
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.shadowColor = color;
|
||||||
|
ctx.shadowBlur = 8;
|
||||||
|
ctx.globalAlpha = isClosed;
|
||||||
|
ctx.fillRect(cx - laserWidth/2, cy - 1, laserWidth, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isClosed < 0.99) {
|
||||||
|
const alphaMult = (1 - isClosed);
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(cx + gazeX, cy + gazeY);
|
||||||
|
|
||||||
|
// Outer cyber ring
|
||||||
|
ctx.strokeStyle = isDarkMode ? "rgba(255,255,255,0.7)" : "rgba(0,0,0,0.6)";
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.globalAlpha = alphaMult;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, 10, 0, Math.PI * 2);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Inner dot
|
||||||
|
ctx.fillStyle = isDarkMode ? "rgba(255,255,255,0.9)" : "rgba(0,0,0,0.8)";
|
||||||
|
ctx.shadowBlur = 5;
|
||||||
|
ctx.shadowColor = isDarkMode ? "white" : "black";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, 3.5, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Cyber crosshairs
|
||||||
|
ctx.strokeStyle = isDarkMode ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.4)";
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.shadowBlur = 0;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(-16, 0); ctx.lineTo(-8, 0);
|
||||||
|
ctx.moveTo(16, 0); ctx.lineTo(8, 0);
|
||||||
|
ctx.moveTo(0, -16); ctx.lineTo(0, -8);
|
||||||
|
ctx.moveTo(0, 16); ctx.lineTo(0, 8);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Arrows
|
||||||
|
ctx.fillStyle = isDarkMode ? "white" : "black";
|
||||||
|
ctx.font = "10px monospace";
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.textBaseline = "middle";
|
||||||
|
ctx.globalAlpha = (dir === 1 ? 1.0 : 0.2) * alphaMult;
|
||||||
|
ctx.fillText("▴", 0, -20);
|
||||||
|
ctx.globalAlpha = (dir === 2 ? 1.0 : 0.2) * alphaMult;
|
||||||
|
ctx.fillText("▾", 0, 22);
|
||||||
|
ctx.globalAlpha = (dir === 3 ? 1.0 : 0.2) * alphaMult;
|
||||||
|
ctx.fillText("◂", -22, 1);
|
||||||
|
ctx.globalAlpha = (dir === 4 ? 1.0 : 0.2) * alphaMult;
|
||||||
|
ctx.fillText("▸", 22, 1);
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
if (!engine) return requestAnimationFrame(render);
|
||||||
|
|
||||||
|
engine.tick();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw Eyes
|
||||||
|
const eyeLen = engine.eye_len();
|
||||||
|
if (eyeLen >= 6) {
|
||||||
|
const eyeData = new Float32Array(wasm.memory.buffer, engine.eye_ptr(), eyeLen);
|
||||||
|
const cx = eyeData[0];
|
||||||
|
const cy = eyeData[1];
|
||||||
|
const gazeX = eyeData[2];
|
||||||
|
const gazeY = eyeData[3];
|
||||||
|
const dir = eyeData[4];
|
||||||
|
const isClosed = eyeData[5];
|
||||||
|
|
||||||
|
const gap = isMobile ? 65 : 75;
|
||||||
|
ctx.globalAlpha = 1.0;
|
||||||
|
drawEye(ctx, cx - gap, cy, gazeX, gazeY, dir, isClosed);
|
||||||
|
drawEye(ctx, cx + gap, cy, gazeX, gazeY, dir, isClosed);
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
+131
-38
@@ -107,17 +107,31 @@ pub struct MatrixEngine {
|
|||||||
bg_buffer: Vec<f32>,
|
bg_buffer: Vec<f32>,
|
||||||
glitch_buffer: Vec<f32>,
|
glitch_buffer: Vec<f32>,
|
||||||
glass_buffer: Vec<f32>,
|
glass_buffer: Vec<f32>,
|
||||||
|
eye_buffer: Vec<f32>,
|
||||||
|
|
||||||
mouse_x: f32,
|
mouse_x: f32,
|
||||||
mouse_y: f32,
|
mouse_y: f32,
|
||||||
is_drawing: bool,
|
is_drawing: bool,
|
||||||
is_mobile: bool,
|
is_mobile: bool,
|
||||||
|
is_vpn_on: bool,
|
||||||
tick_count: u32,
|
tick_count: u32,
|
||||||
emp_timer: f32,
|
emp_timer: f32,
|
||||||
emp_x: f32,
|
emp_x: f32,
|
||||||
emp_y: f32,
|
emp_y: f32,
|
||||||
tear_y: f32,
|
tear_y: f32,
|
||||||
tear_timer: f32,
|
tear_timer: f32,
|
||||||
|
|
||||||
|
// Eye state
|
||||||
|
blink_timer: f32,
|
||||||
|
last_interaction: f32,
|
||||||
|
gaze_target_x: f32,
|
||||||
|
gaze_target_y: f32,
|
||||||
|
gaze_x: f32,
|
||||||
|
gaze_y: f32,
|
||||||
|
eye_closedness: f32,
|
||||||
|
eye_closedness_vel: f32,
|
||||||
|
eye_anchor_y: f32,
|
||||||
|
mobile_wander_timer: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@@ -140,16 +154,28 @@ impl MatrixEngine {
|
|||||||
bg_buffer: Vec::new(),
|
bg_buffer: Vec::new(),
|
||||||
glitch_buffer: Vec::new(),
|
glitch_buffer: Vec::new(),
|
||||||
glass_buffer: Vec::new(),
|
glass_buffer: Vec::new(),
|
||||||
mouse_x: -1000.0,
|
eye_buffer: Vec::new(),
|
||||||
mouse_y: -1000.0,
|
mouse_x: width as f32 / 2.0,
|
||||||
|
mouse_y: height as f32 / 2.0,
|
||||||
is_drawing: false,
|
is_drawing: false,
|
||||||
is_mobile: false,
|
is_mobile: false,
|
||||||
|
is_vpn_on: false,
|
||||||
tick_count: 0,
|
tick_count: 0,
|
||||||
emp_timer: 0.0,
|
emp_timer: 0.0,
|
||||||
emp_x: 0.0,
|
emp_x: 0.0,
|
||||||
emp_y: 0.0,
|
emp_y: 0.0,
|
||||||
tear_y: 0.0,
|
tear_y: 0.0,
|
||||||
tear_timer: 0.0,
|
tear_timer: 0.0,
|
||||||
|
blink_timer: 0.0,
|
||||||
|
last_interaction: 0.0,
|
||||||
|
gaze_target_x: width as f32 / 2.0,
|
||||||
|
gaze_target_y: height as f32 / 2.0,
|
||||||
|
gaze_x: 0.0,
|
||||||
|
gaze_y: 0.0,
|
||||||
|
eye_closedness: 0.0,
|
||||||
|
eye_closedness_vel: 0.0,
|
||||||
|
eye_anchor_y: 0.0,
|
||||||
|
mobile_wander_timer: 0.0,
|
||||||
};
|
};
|
||||||
engine.init_drops();
|
engine.init_drops();
|
||||||
engine
|
engine
|
||||||
@@ -195,6 +221,9 @@ impl MatrixEngine {
|
|||||||
pub fn update_mouse(&mut self, x: f32, y: f32) {
|
pub fn update_mouse(&mut self, x: f32, y: f32) {
|
||||||
self.mouse_x = x;
|
self.mouse_x = x;
|
||||||
self.mouse_y = y;
|
self.mouse_y = y;
|
||||||
|
self.gaze_target_x = x;
|
||||||
|
self.gaze_target_y = y;
|
||||||
|
self.last_interaction = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_drawing(&mut self, is_drawing: bool) {
|
pub fn set_drawing(&mut self, is_drawing: bool) {
|
||||||
@@ -205,6 +234,15 @@ impl MatrixEngine {
|
|||||||
self.is_mobile = is_mobile;
|
self.is_mobile = is_mobile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn set_eye_anchor(&mut self, cy: f32) {
|
||||||
|
self.eye_anchor_y = cy;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_vpn_status(&mut self, is_vpn_on: bool) {
|
||||||
|
self.is_vpn_on = is_vpn_on;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn trigger_glitch(&mut self, x: f32, y: f32) {
|
pub fn trigger_glitch(&mut self, x: f32, y: f32) {
|
||||||
self.emp_timer = 60.0;
|
self.emp_timer = 60.0;
|
||||||
self.emp_x = x;
|
self.emp_x = x;
|
||||||
@@ -215,14 +253,93 @@ impl MatrixEngine {
|
|||||||
let block = GlitchBlock::new(x, y, &mut self.rng);
|
let block = GlitchBlock::new(x, y, &mut self.rng);
|
||||||
self.glitch_blocks.push(block);
|
self.glitch_blocks.push(block);
|
||||||
}
|
}
|
||||||
|
self.update_mouse(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self) {
|
pub fn tick(&mut self) {
|
||||||
self.tick_count = self.tick_count.wrapping_add(1);
|
self.tick_count = self.tick_count.wrapping_add(1);
|
||||||
|
self.last_interaction += 1.0;
|
||||||
|
|
||||||
self.glitch_buffer.clear();
|
self.glitch_buffer.clear();
|
||||||
self.glass_buffer.clear();
|
self.glass_buffer.clear();
|
||||||
|
|
||||||
|
// --- Eye Logic ---
|
||||||
|
// Calculate container center
|
||||||
|
let cx = self.width as f32 / 2.0;
|
||||||
|
let cy = if self.eye_anchor_y > 0.0 { self.eye_anchor_y } else { if self.is_mobile { 150.0 } else { 180.0 } };
|
||||||
|
|
||||||
|
// Random wander for mobile
|
||||||
|
if self.is_mobile && self.last_interaction > 150.0 {
|
||||||
|
self.mobile_wander_timer += 1.0;
|
||||||
|
if self.mobile_wander_timer > 108.0 { // ~1.8s
|
||||||
|
self.mobile_wander_timer = 0.0;
|
||||||
|
if self.rng.gen_bool(0.8) {
|
||||||
|
self.gaze_target_x = cx + self.rng.gen_range(-100.0, 100.0);
|
||||||
|
self.gaze_target_y = cy + self.rng.gen_range(-100.0, 100.0);
|
||||||
|
if self.rng.gen_bool(0.4) {
|
||||||
|
self.blink_timer = 12.0; // micro-blink
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.gaze_target_x = cx;
|
||||||
|
self.gaze_target_y = cy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dx = self.gaze_target_x - cx;
|
||||||
|
let dy = self.gaze_target_y - cy;
|
||||||
|
|
||||||
|
let mut dir = 0.0;
|
||||||
|
if dx.abs() < 60.0 && dy.abs() < 60.0 {
|
||||||
|
dir = 0.0; // center
|
||||||
|
} else if dx.abs() > dy.abs() {
|
||||||
|
dir = if dx > 0.0 { 4.0 } else { 3.0 }; // 4=right, 3=left
|
||||||
|
} else {
|
||||||
|
dir = if dy > 0.0 { 2.0 } else { 1.0 }; // 2=down, 1=up
|
||||||
|
}
|
||||||
|
|
||||||
|
let distance = (dx * dx + dy * dy).sqrt();
|
||||||
|
let max_radius = 20.0;
|
||||||
|
let target_dist = (distance * 0.1).min(max_radius);
|
||||||
|
let angle = dy.atan2(dx);
|
||||||
|
|
||||||
|
let target_gaze_x = angle.cos() * target_dist;
|
||||||
|
let target_gaze_y = angle.sin() * target_dist;
|
||||||
|
|
||||||
|
// Spring physics interpolation (smoother gaze)
|
||||||
|
self.gaze_x += (target_gaze_x - self.gaze_x) * 0.12;
|
||||||
|
self.gaze_y += (target_gaze_y - self.gaze_y) * 0.12;
|
||||||
|
|
||||||
|
// Blinking
|
||||||
|
if self.blink_timer > 0.0 {
|
||||||
|
self.blink_timer -= 1.0;
|
||||||
|
} else if !self.is_vpn_on {
|
||||||
|
if self.rng.gen_bool(0.0015) { // random blink
|
||||||
|
self.blink_timer = 16.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_closedness = if self.is_vpn_on || self.blink_timer > 0.0 { 1.0 } else { 0.0 };
|
||||||
|
|
||||||
|
// Springy blink physics (slower, smoother)
|
||||||
|
let diff = target_closedness - self.eye_closedness;
|
||||||
|
self.eye_closedness_vel += diff * 0.06; // lower stiffness (slower acceleration)
|
||||||
|
self.eye_closedness_vel *= 0.88; // higher damping (longer slide, less snappy)
|
||||||
|
self.eye_closedness += self.eye_closedness_vel;
|
||||||
|
|
||||||
|
// Clamp to prevent extreme glitches, but allow small spring bounce
|
||||||
|
if self.eye_closedness < -0.15 { self.eye_closedness = -0.15; }
|
||||||
|
if self.eye_closedness > 1.15 { self.eye_closedness = 1.15; }
|
||||||
|
|
||||||
|
self.eye_buffer.clear();
|
||||||
|
self.eye_buffer.push(cx);
|
||||||
|
self.eye_buffer.push(cy);
|
||||||
|
self.eye_buffer.push(self.gaze_x);
|
||||||
|
self.eye_buffer.push(self.gaze_y);
|
||||||
|
self.eye_buffer.push(dir);
|
||||||
|
self.eye_buffer.push(self.eye_closedness);
|
||||||
|
// --- End Eye Logic ---
|
||||||
|
|
||||||
if !self.is_mobile {
|
if !self.is_mobile {
|
||||||
if self.rng.gen_bool(0.01) {
|
if self.rng.gen_bool(0.01) {
|
||||||
let rx = self.rng.gen_range(0.0, self.width as f32);
|
let rx = self.rng.gen_range(0.0, self.width as f32);
|
||||||
@@ -262,7 +379,6 @@ impl MatrixEngine {
|
|||||||
self.bg_buffer[base_idx] = x;
|
self.bg_buffer[base_idx] = x;
|
||||||
self.bg_buffer[base_idx + 1] = y;
|
self.bg_buffer[base_idx + 1] = y;
|
||||||
self.bg_buffer[base_idx + 2] = char_idx as f32;
|
self.bg_buffer[base_idx + 2] = char_idx as f32;
|
||||||
// Derive alpha from cell hash to avoid RNG overhead and create stable matrix walls
|
|
||||||
self.bg_buffer[base_idx + 3] = 0.02 + ((hash % 100) as f32 * 0.0008);
|
self.bg_buffer[base_idx + 3] = 0.02 + ((hash % 100) as f32 * 0.0008);
|
||||||
|
|
||||||
drop.y += drop.speed;
|
drop.y += drop.speed;
|
||||||
@@ -274,16 +390,8 @@ impl MatrixEngine {
|
|||||||
|
|
||||||
let emp_rad = (60.0 - self.emp_timer) * 15.0;
|
let emp_rad = (60.0 - self.emp_timer) * 15.0;
|
||||||
let emp_rad_sq = emp_rad * emp_rad;
|
let emp_rad_sq = emp_rad * emp_rad;
|
||||||
let emp_r20_sq = if emp_rad > 20.0 {
|
let emp_r20_sq = if emp_rad > 20.0 { (emp_rad - 20.0) * (emp_rad - 20.0) } else { 0.0 };
|
||||||
(emp_rad - 20.0) * (emp_rad - 20.0)
|
let emp_r80_sq = if emp_rad > 80.0 { (emp_rad - 80.0) * (emp_rad - 80.0) } else { 0.0 };
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
};
|
|
||||||
let emp_r80_sq = if emp_rad > 80.0 {
|
|
||||||
(emp_rad - 80.0) * (emp_rad - 80.0)
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
};
|
|
||||||
|
|
||||||
let fg_spacing = if self.is_mobile { 2.0 } else { 1.0 } * self.font_size as f32;
|
let fg_spacing = if self.is_mobile { 2.0 } else { 1.0 } * self.font_size as f32;
|
||||||
|
|
||||||
@@ -336,7 +444,6 @@ impl MatrixEngine {
|
|||||||
.wrapping_add(time_shift);
|
.wrapping_add(time_shift);
|
||||||
let mut char_idx = (hash as usize) % self.charset_len;
|
let mut char_idx = (hash as usize) % self.charset_len;
|
||||||
|
|
||||||
// Derive visual traits from hash to save massive RNG overhead
|
|
||||||
let mut is_head = (hash % 100) < 15;
|
let mut is_head = (hash % 100) < 15;
|
||||||
let mut target_alpha = if is_head { 1.0 } else { 0.6 };
|
let mut target_alpha = if is_head { 1.0 } else { 0.6 };
|
||||||
|
|
||||||
@@ -406,28 +513,14 @@ impl MatrixEngine {
|
|||||||
self.glass_symbols.retain(|s| s.life > 0.0);
|
self.glass_symbols.retain(|s| s.life > 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fg_ptr(&self) -> *const f32 {
|
pub fn fg_ptr(&self) -> *const f32 { self.fg_buffer.as_ptr() }
|
||||||
self.fg_buffer.as_ptr()
|
pub fn fg_len(&self) -> usize { self.fg_buffer.len() }
|
||||||
}
|
pub fn bg_ptr(&self) -> *const f32 { self.bg_buffer.as_ptr() }
|
||||||
pub fn fg_len(&self) -> usize {
|
pub fn bg_len(&self) -> usize { self.bg_buffer.len() }
|
||||||
self.fg_buffer.len()
|
pub fn glitch_ptr(&self) -> *const f32 { self.glitch_buffer.as_ptr() }
|
||||||
}
|
pub fn glitch_len(&self) -> usize { self.glitch_buffer.len() }
|
||||||
pub fn bg_ptr(&self) -> *const f32 {
|
pub fn glass_ptr(&self) -> *const f32 { self.glass_buffer.as_ptr() }
|
||||||
self.bg_buffer.as_ptr()
|
pub fn glass_len(&self) -> usize { self.glass_buffer.len() }
|
||||||
}
|
pub fn eye_ptr(&self) -> *const f32 { self.eye_buffer.as_ptr() }
|
||||||
pub fn bg_len(&self) -> usize {
|
pub fn eye_len(&self) -> usize { self.eye_buffer.len() }
|
||||||
self.bg_buffer.len()
|
|
||||||
}
|
|
||||||
pub fn glitch_ptr(&self) -> *const f32 {
|
|
||||||
self.glitch_buffer.as_ptr()
|
|
||||||
}
|
|
||||||
pub fn glitch_len(&self) -> usize {
|
|
||||||
self.glitch_buffer.len()
|
|
||||||
}
|
|
||||||
pub fn glass_ptr(&self) -> *const f32 {
|
|
||||||
self.glass_buffer.as_ptr()
|
|
||||||
}
|
|
||||||
pub fn glass_len(&self) -> usize {
|
|
||||||
self.glass_buffer.len()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user