Files
at-mintel/packages/gatekeeper/src/components/gate-scene.tsx
Marc Mintel 36ed26ad79
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
feat(gatekeeper): major UI upgrade - high-fidelity light theme, iridescent mouse-reactive form, and enhanced background animation
2026-02-28 21:48:03 +01:00

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>
);
}