project structure
This commit is contained in:
+329
@@ -0,0 +1,329 @@
|
||||
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=UnifrakturMaguntia&family=IM+Fell+English+SC&display=swap');
|
||||
|
||||
.font-fraktur { font-family: 'UnifrakturMaguntia', serif; }
|
||||
.font-fell { font-family: 'IM Fell English SC', 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user