Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧪 Test (push) Successful in 1m10s
Monorepo Pipeline / 🧹 Lint (push) Failing after 3m15s
Monorepo Pipeline / 🏗️ Build (push) Successful in 1m53s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
🏥 Server Maintenance / 🧹 Prune & Clean (push) Failing after 4s
128 lines
3.6 KiB
TypeScript
128 lines
3.6 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
|
|
export function GateScene() {
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
const mouse = useRef({ x: -1000, y: -1000 });
|
|
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) return;
|
|
|
|
const FONT_SIZE = 13;
|
|
const COL_GAP = 18;
|
|
const ROW_GAP = 20;
|
|
|
|
interface Cell {
|
|
char: string;
|
|
alpha: number;
|
|
targetAlpha: number;
|
|
speed: number;
|
|
nextChange: number;
|
|
}
|
|
|
|
let cells: Cell[][] = [];
|
|
let cols = 0;
|
|
let rows = 0;
|
|
let animId: number;
|
|
|
|
const init = () => {
|
|
cols = Math.ceil(canvas.width / COL_GAP);
|
|
rows = Math.ceil(canvas.height / ROW_GAP);
|
|
cells = Array.from({ length: cols }, () =>
|
|
Array.from({ length: rows }, () => ({
|
|
char: Math.random() > 0.5 ? "1" : "0",
|
|
alpha: Math.random() * 0.08,
|
|
targetAlpha: Math.random() * 0.15,
|
|
speed: 0.008 + Math.random() * 0.02,
|
|
nextChange: Math.floor(Math.random() * 80),
|
|
})),
|
|
);
|
|
};
|
|
|
|
const onMouseMove = (e: MouseEvent) => {
|
|
mouse.current = { x: e.clientX, y: e.clientY };
|
|
};
|
|
|
|
const resize = () => {
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
init();
|
|
};
|
|
|
|
let frame = 0;
|
|
|
|
const draw = () => {
|
|
frame++;
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
ctx.font = `${FONT_SIZE}px 'Courier New', monospace`;
|
|
|
|
for (let c = 0; c < cols; c++) {
|
|
for (let r = 0; r < rows; r++) {
|
|
const cell = cells[c][r];
|
|
const x = c * COL_GAP;
|
|
const y = r * ROW_GAP + FONT_SIZE;
|
|
|
|
// Mouse proximity influence
|
|
const dx = mouse.current.x - x;
|
|
const dy = mouse.current.y - y;
|
|
const distSq = dx * dx + dy * dy;
|
|
const proximity = Math.max(0, 1 - Math.sqrt(distSq) / 250);
|
|
|
|
// Nudge alpha toward target
|
|
cell.alpha += (cell.targetAlpha - cell.alpha) * cell.speed;
|
|
|
|
// More aggressive random behavior
|
|
if (frame >= cell.nextChange) {
|
|
cell.targetAlpha = Math.random() * 0.25; // Higher max alpha
|
|
cell.speed = 0.01 + Math.random() * 0.03; // Faster transitions
|
|
cell.nextChange = frame + 20 + Math.floor(Math.random() * 80); // More frequent changes
|
|
|
|
// Higher flip probability near mouse
|
|
const flipProb = 0.3 + proximity * 0.5;
|
|
if (Math.random() < flipProb) {
|
|
cell.char = cell.char === "0" ? "1" : "0";
|
|
}
|
|
}
|
|
|
|
const a = Math.min(0.4, cell.alpha + proximity * 0.35);
|
|
if (a < 0.01) continue;
|
|
|
|
// Dark chars on light background
|
|
ctx.fillStyle = `rgba(0, 0, 0, ${a})`;
|
|
ctx.fillText(cell.char, x, y);
|
|
}
|
|
}
|
|
|
|
animId = requestAnimationFrame(draw);
|
|
};
|
|
|
|
resize();
|
|
window.addEventListener("resize", resize);
|
|
window.addEventListener("mousemove", onMouseMove);
|
|
animId = requestAnimationFrame(draw);
|
|
|
|
return () => {
|
|
cancelAnimationFrame(animId);
|
|
window.removeEventListener("resize", resize);
|
|
window.removeEventListener("mousemove", onMouseMove);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div className="absolute inset-0 pointer-events-none z-0 bg-[#f5f5f7]">
|
|
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full" />
|
|
<div
|
|
className="absolute inset-0 pointer-events-none"
|
|
style={{
|
|
background:
|
|
"radial-gradient(ellipse 55% 65% at 50% 50%, rgba(245,245,247,0.7) 0%, transparent 100%)",
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|