330 lines
12 KiB
TypeScript
330 lines
12 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
|
import type { JSX } from "react";
|
|
|
|
const REDIRECT_URL: string = "https://meet.google.com";
|
|
|
|
interface ScheduleFn {
|
|
(): number;
|
|
}
|
|
|
|
interface Refs {
|
|
eyeRef: React.RefObject<HTMLDivElement | null>;
|
|
pupilRef: React.RefObject<SVGGElement | null>;
|
|
}
|
|
|
|
export default function WatchingYou(): JSX.Element {
|
|
const eyeRef = useRef<HTMLDivElement | null>(null);
|
|
const pupilRef = useRef<SVGGElement | null>(null);
|
|
const [blinking, setBlinking] = useState<boolean>(false);
|
|
const [glitching, setGlitching] = useState<boolean>(false);
|
|
|
|
useEffect(() => {
|
|
const handleMouseMove = (e: MouseEvent) => {
|
|
if (!eyeRef.current || !pupilRef.current) return;
|
|
const rect = eyeRef.current.getBoundingClientRect();
|
|
const cx = rect.left + rect.width / 2;
|
|
const cy = rect.top + rect.height / 2;
|
|
const dx = e.clientX - cx;
|
|
const dy = e.clientY - cy;
|
|
const angle = Math.atan2(dy, dx);
|
|
const dist = Math.min(Math.hypot(dx, dy), 28);
|
|
pupilRef.current.style.transform = `translate(${Math.cos(angle) * dist}px, ${Math.sin(angle) * dist}px)`;
|
|
};
|
|
window.addEventListener("mousemove", handleMouseMove);
|
|
return () => window.removeEventListener("mousemove", handleMouseMove);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const scheduleBlink: ScheduleFn = () => {
|
|
const delay: number = 2500 + Math.random() * 3000;
|
|
return window.setTimeout(() => {
|
|
setBlinking(true);
|
|
setTimeout(() => {
|
|
setBlinking(false);
|
|
blink = scheduleBlink();
|
|
}, 180);
|
|
}, delay);
|
|
};
|
|
let blink: number = scheduleBlink();
|
|
return () => clearTimeout(blink);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const scheduleGlitch: ScheduleFn = () => {
|
|
const delay: number = 4000 + Math.random() * 6000;
|
|
return window.setTimeout(() => {
|
|
setGlitching(true);
|
|
setTimeout(() => {
|
|
setGlitching(false);
|
|
glitch = scheduleGlitch();
|
|
}, 400);
|
|
}, delay);
|
|
};
|
|
let glitch: number = scheduleGlitch();
|
|
return () => clearTimeout(glitch);
|
|
}, []);
|
|
|
|
const handleRedirect = (): void => {
|
|
window.location.href = REDIRECT_URL;
|
|
};
|
|
|
|
return (
|
|
<div style={{ minHeight: "100vh", background: "#080808", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", overflow: "hidden", position: "relative", userSelect: "none" }}>
|
|
<style>{`
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=Merriweather:wght@300;400;700&display=swap');
|
|
|
|
/* Use readable, modern fonts */
|
|
.font-fraktur { font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; }
|
|
.font-fell { font-family: 'Merriweather', Georgia, 'Times New Roman', serif; }
|
|
|
|
.scanlines::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: repeating-linear-gradient(
|
|
0deg,
|
|
transparent,
|
|
transparent 2px,
|
|
rgba(0,0,0,0.08) 2px,
|
|
rgba(0,0,0,0.08) 4px
|
|
);
|
|
pointer-events: none;
|
|
}
|
|
|
|
@keyframes flicker {
|
|
0%, 95%, 100% { opacity: 1; }
|
|
96% { opacity: 0.85; }
|
|
97% { opacity: 1; }
|
|
98% { opacity: 0.9; }
|
|
}
|
|
|
|
@keyframes pulse-red {
|
|
0%, 100% { filter: drop-shadow(0 0 12px #cc0000aa); }
|
|
50% { filter: drop-shadow(0 0 28px #ff0000cc) drop-shadow(0 0 60px #cc000066); }
|
|
}
|
|
|
|
@keyframes glitch-shift {
|
|
0% { transform: translateX(0); clip-path: inset(0 0 100% 0); }
|
|
20% { transform: translateX(-4px); clip-path: inset(20% 0 60% 0); }
|
|
40% { transform: translateX(4px); clip-path: inset(60% 0 20% 0); }
|
|
60% { transform: translateX(-2px); clip-path: inset(40% 0 40% 0); }
|
|
80% { transform: translateX(0); clip-path: inset(10% 0 80% 0); }
|
|
100% { transform: translateX(0); clip-path: inset(0 0 100% 0); }
|
|
}
|
|
|
|
@keyframes vignette-pulse {
|
|
0%, 100% { opacity: 0.7; }
|
|
50% { opacity: 1; }
|
|
}
|
|
|
|
.eye-container {
|
|
animation: pulse-red 3s ease-in-out infinite, flicker 8s linear infinite;
|
|
}
|
|
|
|
.upper-lid {
|
|
transform-box: fill-box;
|
|
transform-origin: center bottom;
|
|
transition: transform 0.1s ease-in-out;
|
|
}
|
|
.lower-lid {
|
|
transform-box: fill-box;
|
|
transform-origin: center top;
|
|
transition: transform 0.1s ease-in-out;
|
|
}
|
|
|
|
.glitch-text::before,
|
|
.glitch-text::after {
|
|
content: attr(data-text);
|
|
position: absolute;
|
|
top: 0; left: 0;
|
|
width: 100%;
|
|
}
|
|
.glitch-text::before {
|
|
color: #ff0000;
|
|
animation: glitch-shift 0.4s steps(1) forwards;
|
|
}
|
|
.glitch-text::after {
|
|
color: #00ffff22;
|
|
animation: glitch-shift 0.4s steps(1) reverse forwards;
|
|
}
|
|
|
|
.btn-horror {
|
|
position: relative;
|
|
overflow: hidden;
|
|
background: transparent;
|
|
letter-spacing: 0.15em;
|
|
transition: all 0.2s ease;
|
|
cursor: pointer;
|
|
}
|
|
.btn-horror::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: #cc0000;
|
|
transform: scaleX(0);
|
|
transform-origin: left;
|
|
transition: transform 0.25s ease;
|
|
z-index: 0;
|
|
}
|
|
.btn-horror span {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
.btn-horror:hover::before { transform: scaleX(1); }
|
|
.btn-horror:hover { color: #fff !important; border-color: #cc0000 !important; }
|
|
.btn-horror:active { transform: scale(0.97); }
|
|
|
|
@keyframes float-in {
|
|
from { opacity: 0; transform: translateY(30px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
.animate-float-in { animation: float-in 1.2s ease forwards; }
|
|
.delay-1 { animation-delay: 0.4s; opacity: 0; }
|
|
.delay-2 { animation-delay: 0.8s; opacity: 0; }
|
|
.delay-3 { animation-delay: 1.2s; opacity: 0; }
|
|
.delay-4 { animation-delay: 1.6s; opacity: 0; }
|
|
`}</style>
|
|
|
|
{/* Vignette overlay */}
|
|
<div
|
|
style={{
|
|
pointerEvents: "none", position: "fixed", inset: 0, zIndex: 10,
|
|
background: "radial-gradient(ellipse at center, transparent 30%, rgba(0,0,0,0.85) 100%)",
|
|
animation: "vignette-pulse 4s ease-in-out infinite",
|
|
}}
|
|
/>
|
|
|
|
{/* Scanlines overlay */}
|
|
<div className="scanlines" style={{ pointerEvents: "none", position: "fixed", inset: 0, zIndex: 10 }} />
|
|
|
|
{/* Content */}
|
|
<div style={{ position: "relative", zIndex: 20, display: "flex", flexDirection: "column", alignItems: "center", gap: "1.25rem", padding: "2rem 2rem", textAlign: "center" }}>
|
|
|
|
{/* Eye SVG */}
|
|
<div className="eye-container animate-float-in" ref={eyeRef}>
|
|
<svg width="180" height="120" viewBox="-90 -60 180 120" xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
<radialGradient id="irisGrad" cx="40%" cy="35%">
|
|
<stop offset="0%" stopColor="#ff3333" />
|
|
<stop offset="50%" stopColor="#8b0000" />
|
|
<stop offset="100%" stopColor="#2a0000" />
|
|
</radialGradient>
|
|
<radialGradient id="scleraGrad" cx="50%" cy="40%">
|
|
<stop offset="0%" stopColor="#f5e6e6" />
|
|
<stop offset="100%" stopColor="#c9b0b0" />
|
|
</radialGradient>
|
|
<clipPath id="eyeClip">
|
|
<path d="M-80,0 Q-40,-55 0,-55 Q40,-55 80,0 Q40,55 0,55 Q-40,55 -80,0 Z" />
|
|
</clipPath>
|
|
</defs>
|
|
|
|
{/* Sclera */}
|
|
<path
|
|
d="M-80,0 Q-40,-55 0,-55 Q40,-55 80,0 Q40,55 0,55 Q-40,55 -80,0 Z"
|
|
fill="url(#scleraGrad)"
|
|
/>
|
|
|
|
{/* Iris + pupil group (mouse-tracked) */}
|
|
<g clipPath="url(#eyeClip)">
|
|
<g ref={pupilRef} style={{ transition: "transform 0.08s ease-out" }}>
|
|
<circle cx="0" cy="0" r="32" fill="url(#irisGrad)" />
|
|
{/* Iris detail rings */}
|
|
<circle cx="0" cy="0" r="28" fill="none" stroke="#6b000088" strokeWidth="1.5" />
|
|
<circle cx="0" cy="0" r="22" fill="none" stroke="#9b000066" strokeWidth="1" />
|
|
{/* Pupil */}
|
|
<circle cx="0" cy="0" r="14" fill="#050000" />
|
|
{/* Catchlight */}
|
|
<circle cx="-7" cy="-7" r="4" fill="rgba(255,255,255,0.35)" />
|
|
</g>
|
|
</g>
|
|
|
|
{/* Eyelids (blink) — filled half-shapes scale from center line */}
|
|
<path
|
|
className="upper-lid"
|
|
d="M-80,0 Q-40,-55 0,-55 Q40,-55 80,0 Z"
|
|
fill="#080808"
|
|
style={{ transform: blinking ? "scaleY(1)" : "scaleY(0)" }}
|
|
/>
|
|
<path
|
|
className="lower-lid"
|
|
d="M-80,0 Q-40,55 0,55 Q40,55 80,0 Z"
|
|
fill="#080808"
|
|
style={{ transform: blinking ? "scaleY(1)" : "scaleY(0)" }}
|
|
/>
|
|
|
|
{/* Eyelash hints */}
|
|
<path
|
|
d="M-80,0 Q-40,-55 0,-55 Q40,-55 80,0"
|
|
fill="none"
|
|
stroke="#1a0a0a"
|
|
strokeWidth="4"
|
|
strokeLinecap="round"
|
|
/>
|
|
<path
|
|
d="M-80,0 Q-40,55 0,55 Q40,55 80,0"
|
|
fill="none"
|
|
stroke="#1a0a0a"
|
|
strokeWidth="4"
|
|
strokeLinecap="round"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
|
|
{/* Main heading */}
|
|
<div
|
|
className={`relative animate-float-in delay-1 ${glitching ? "glitch-text" : ""}`}
|
|
data-text="I'm Watching You,"
|
|
>
|
|
<h1 className="font-fraktur leading-tight" style={{ fontSize: "clamp(2.2rem, 6vw, 4rem)", color: "#ff4444" }}>
|
|
I'm Watching You,
|
|
</h1>
|
|
</div>
|
|
|
|
{/* Name */}
|
|
<div
|
|
className={`relative animate-float-in delay-2 ${glitching ? "glitch-text" : ""}`}
|
|
style={{ marginTop: "-0.5rem" }}
|
|
data-text="Aariz."
|
|
>
|
|
<h2
|
|
className="font-fraktur"
|
|
style={{ fontSize: "clamp(3rem, 10vw, 6.5rem)", lineHeight: 1, color: "#ff2222", textShadow: "0 0 40px #cc000099" }}
|
|
>
|
|
Aariz.
|
|
</h2>
|
|
</div>
|
|
|
|
{/* Subtitle */}
|
|
<p
|
|
className="font-fell uppercase animate-float-in delay-3"
|
|
style={{ letterSpacing: "0.3em", fontSize: "0.8rem", color: "#cc6666" }}
|
|
>
|
|
Every move. Every click. Every breath.
|
|
</p>
|
|
|
|
{/* Buttons */}
|
|
<div style={{ display: "flex", flexWrap: "wrap", gap: "1rem", marginTop: "0.5rem", justifyContent: "center" }} className="animate-float-in delay-4">
|
|
<button
|
|
onClick={handleRedirect}
|
|
className="btn-horror font-fell"
|
|
style={{ color: "#ff4444", border: "1px solid #cc0000", padding: "0.75rem 2rem", fontSize: "0.8rem", textTransform: "uppercase", letterSpacing: "0.15em" }}
|
|
>
|
|
<span>Beg for Forgiveness</span>
|
|
</button>
|
|
<button
|
|
onClick={handleRedirect}
|
|
className="btn-horror font-fell"
|
|
style={{ color: "#cc4444", border: "1px solid #6b1515", padding: "0.75rem 2rem", fontSize: "0.8rem", textTransform: "uppercase", letterSpacing: "0.15em" }}
|
|
>
|
|
<span>Accept Your Fate</span>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Footer whisper */}
|
|
<p className="font-fell animate-float-in delay-4" style={{ letterSpacing: "0.2em", fontSize: "0.75rem", color: "#884444", marginTop: "0.5rem" }}>
|
|
There is no escape.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |