404lab 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/core/templates.js +5 -3
- package/package.json +1 -1
- package/templates/AmongUs.tsx +92 -109
- package/templates/BlueGlitch.tsx +34 -32
- package/templates/BugGame.tsx +479 -0
- package/templates/GeeksforGeeks.tsx +38 -44
- package/templates/Google.tsx +51 -37
- package/templates/MacOs.tsx +82 -68
- package/templates/ModernPage.tsx +45 -24
- package/templates/Particles.tsx +35 -18
- package/templates/Poet.tsx +99 -71
- package/templates/RetroTv.tsx +22 -18
- package/templates/SimplePage.tsx +32 -17
- package/templates/Snow.tsx +54 -21
- package/templates/StoneAge.tsx +75 -33
- package/templates/StrangerThings.tsx +11 -10
- package/templates/{Terminal404.tsx → Terminal.tsx} +20 -10
- package/templates/Vercel.tsx +78 -32
- package/templates/Void.tsx +345 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { cn } from "@/components/ui/cn";
|
|
6
|
+
|
|
7
|
+
export default function Void({ className }: { className?: string }) {
|
|
8
|
+
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
9
|
+
const [open, setOpen] = useState(false);
|
|
10
|
+
const [titleIdx, setTitleIdx] = useState(0);
|
|
11
|
+
const [isChanging, setIsChanging] = useState(false);
|
|
12
|
+
|
|
13
|
+
const messages = [
|
|
14
|
+
{
|
|
15
|
+
title: ["You've drifted beyond", "the known routes"],
|
|
16
|
+
subtitle:
|
|
17
|
+
"The page you're seeking doesn't exist—or it's been swallowed by the void. Return to safety below.",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
title: ["Signal lost.", "Reestablishing connection..."],
|
|
21
|
+
subtitle:
|
|
22
|
+
"This route has evaporated into the void. Use the links below to find your way back.",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
title: ["Coordinates unknown.", "Destination not found."],
|
|
26
|
+
subtitle: "You've reached the edge of the map. Turn back before the void consumes you.",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
title: ["404.", "We have a problem."],
|
|
30
|
+
subtitle: "Houston, this page doesn't exist. Abort and return to base.",
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
36
|
+
|
|
37
|
+
let mouseX = 0;
|
|
38
|
+
let mouseY = 0;
|
|
39
|
+
let targetX = 0;
|
|
40
|
+
let targetY = 0;
|
|
41
|
+
|
|
42
|
+
const move = (e: MouseEvent) => {
|
|
43
|
+
targetX = (e.clientX / window.innerWidth - 0.5) * 2;
|
|
44
|
+
targetY = (e.clientY / window.innerHeight - 0.5) * 2;
|
|
45
|
+
mouseX = e.clientX;
|
|
46
|
+
mouseY = e.clientY;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const leave = () => {
|
|
50
|
+
targetX = 0;
|
|
51
|
+
targetY = 0;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
document.addEventListener("mousemove", move);
|
|
55
|
+
document.addEventListener("mouseleave", leave);
|
|
56
|
+
|
|
57
|
+
if (!prefersReducedMotion) {
|
|
58
|
+
const digits = document.querySelectorAll(".void-digit") as NodeListOf<HTMLSpanElement>;
|
|
59
|
+
let lerpX = 0;
|
|
60
|
+
let lerpY = 0;
|
|
61
|
+
let active = false;
|
|
62
|
+
setTimeout(() => (active = true), 1200);
|
|
63
|
+
|
|
64
|
+
const update = () => {
|
|
65
|
+
if (!active) {
|
|
66
|
+
requestAnimationFrame(update);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
lerpX += (targetX - lerpX) * 0.08;
|
|
70
|
+
lerpY += (targetY - lerpY) * 0.08;
|
|
71
|
+
digits.forEach((d, i) => {
|
|
72
|
+
const factor = (i - 1) * 0.5;
|
|
73
|
+
const tx = lerpX * 20 * (1 + factor * 0.5);
|
|
74
|
+
const ty = lerpY * 20 * (1 + factor * 0.5);
|
|
75
|
+
d.style.transform = `translate3d(${tx}px,${ty}px,0) rotateY(${lerpX * 5}deg) rotateX(${-lerpY * 5}deg)`;
|
|
76
|
+
});
|
|
77
|
+
requestAnimationFrame(update);
|
|
78
|
+
};
|
|
79
|
+
update();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const canvas = canvasRef.current;
|
|
83
|
+
let handleResize: () => void;
|
|
84
|
+
|
|
85
|
+
if (canvas) {
|
|
86
|
+
const ctx = canvas.getContext("2d")!;
|
|
87
|
+
let width = window.innerWidth;
|
|
88
|
+
let height = window.innerHeight;
|
|
89
|
+
let particles: any[] = [];
|
|
90
|
+
const maxDistance = 150;
|
|
91
|
+
const mouseRadius = 200;
|
|
92
|
+
|
|
93
|
+
const particleCount = Math.min(120, Math.floor((width * height) / 10000));
|
|
94
|
+
|
|
95
|
+
class Particle {
|
|
96
|
+
x: number;
|
|
97
|
+
y: number;
|
|
98
|
+
vx: number;
|
|
99
|
+
vy: number;
|
|
100
|
+
radius: number;
|
|
101
|
+
baseOpacity: number;
|
|
102
|
+
opacity: number;
|
|
103
|
+
|
|
104
|
+
constructor() {
|
|
105
|
+
this.x = Math.random() * width;
|
|
106
|
+
this.y = Math.random() * height;
|
|
107
|
+
this.vx = (Math.random() - 0.5) * 0.5;
|
|
108
|
+
this.vy = (Math.random() - 0.5) * 0.5;
|
|
109
|
+
this.radius = Math.random() * 2 + 0.5;
|
|
110
|
+
this.baseOpacity = Math.random() * 0.4 + 0.1;
|
|
111
|
+
this.opacity = this.baseOpacity;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
update() {
|
|
115
|
+
const dx = mouseX - this.x;
|
|
116
|
+
const dy = mouseY - this.y;
|
|
117
|
+
const dist = Math.hypot(dx, dy);
|
|
118
|
+
|
|
119
|
+
if (dist < mouseRadius && !prefersReducedMotion) {
|
|
120
|
+
const force = (1 - dist / mouseRadius) * 0.2;
|
|
121
|
+
const angle = Math.atan2(dy, dx);
|
|
122
|
+
this.vx -= Math.cos(angle) * force;
|
|
123
|
+
this.vy -= Math.sin(angle) * force;
|
|
124
|
+
this.opacity = Math.min(1, this.baseOpacity * 4);
|
|
125
|
+
} else {
|
|
126
|
+
this.opacity += (this.baseOpacity - this.opacity) * 0.05;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.vx *= 0.98;
|
|
130
|
+
this.vy *= 0.98;
|
|
131
|
+
this.x += this.vx;
|
|
132
|
+
this.y += this.vy;
|
|
133
|
+
|
|
134
|
+
if (this.x < 0) this.x = width;
|
|
135
|
+
if (this.x > width) this.x = 0;
|
|
136
|
+
if (this.y < 0) this.y = height;
|
|
137
|
+
if (this.y > height) this.y = 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
draw() {
|
|
141
|
+
ctx.beginPath();
|
|
142
|
+
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
|
143
|
+
ctx.fillStyle = `rgba(0,229,199,${this.opacity})`;
|
|
144
|
+
ctx.fill();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const init = () => {
|
|
149
|
+
particles = [];
|
|
150
|
+
for (let i = 0; i < particleCount; i++) particles.push(new Particle());
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const drawConnections = () => {
|
|
154
|
+
for (let i = 0; i < particles.length; i++) {
|
|
155
|
+
for (let j = i + 1; j < particles.length; j++) {
|
|
156
|
+
const dx = particles[i].x - particles[j].x;
|
|
157
|
+
const dy = particles[i].y - particles[j].y;
|
|
158
|
+
const dist = Math.hypot(dx, dy);
|
|
159
|
+
if (dist < maxDistance) {
|
|
160
|
+
const op = (1 - dist / maxDistance) * 0.15;
|
|
161
|
+
ctx.beginPath();
|
|
162
|
+
ctx.moveTo(particles[i].x, particles[i].y);
|
|
163
|
+
ctx.lineTo(particles[j].x, particles[j].y);
|
|
164
|
+
ctx.strokeStyle = `rgba(0,229,199,${op})`;
|
|
165
|
+
ctx.lineWidth = 0.8;
|
|
166
|
+
ctx.stroke();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
handleResize = () => {
|
|
173
|
+
width = window.innerWidth;
|
|
174
|
+
height = window.innerHeight;
|
|
175
|
+
canvas.width = width;
|
|
176
|
+
canvas.height = height;
|
|
177
|
+
init();
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const animate = () => {
|
|
181
|
+
ctx.clearRect(0, 0, width, height);
|
|
182
|
+
particles.forEach((p) => {
|
|
183
|
+
p.update();
|
|
184
|
+
p.draw();
|
|
185
|
+
});
|
|
186
|
+
drawConnections();
|
|
187
|
+
requestAnimationFrame(animate);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
handleResize();
|
|
191
|
+
animate();
|
|
192
|
+
window.addEventListener("resize", handleResize);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return () => {
|
|
196
|
+
document.removeEventListener("mousemove", move);
|
|
197
|
+
document.removeEventListener("mouseleave", leave);
|
|
198
|
+
if (handleResize) window.removeEventListener("resize", handleResize);
|
|
199
|
+
};
|
|
200
|
+
}, []);
|
|
201
|
+
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
const id = setInterval(() => {
|
|
204
|
+
setIsChanging(true);
|
|
205
|
+
setTimeout(() => {
|
|
206
|
+
setTitleIdx((p) => (p + 1) % messages.length);
|
|
207
|
+
setIsChanging(false);
|
|
208
|
+
}, 500);
|
|
209
|
+
}, 6000);
|
|
210
|
+
return () => clearInterval(id);
|
|
211
|
+
}, []);
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div className={cn("relative min-h-screen flex flex-col items-center justify-center bg-[#05050a] text-[#f0f0f3] overflow-hidden font-sans", className)}>
|
|
215
|
+
<style jsx global>{`
|
|
216
|
+
@keyframes fadeIn {
|
|
217
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
218
|
+
to { opacity: 1; transform: translateY(0); }
|
|
219
|
+
}
|
|
220
|
+
@keyframes pulse {
|
|
221
|
+
0%, 100% { opacity: 0.5; }
|
|
222
|
+
50% { opacity: 0.8; }
|
|
223
|
+
}
|
|
224
|
+
.animate-fade-in {
|
|
225
|
+
animation: fadeIn 0.8s ease-out forwards;
|
|
226
|
+
}
|
|
227
|
+
.animate-fade-out {
|
|
228
|
+
opacity: 0;
|
|
229
|
+
transition: opacity 0.5s ease-in-out;
|
|
230
|
+
}
|
|
231
|
+
.animate-fade-in-content {
|
|
232
|
+
opacity: 1;
|
|
233
|
+
transition: opacity 0.5s ease-in-out;
|
|
234
|
+
}
|
|
235
|
+
`}</style>
|
|
236
|
+
|
|
237
|
+
<div className="fixed inset-0 pointer-events-none bg-[radial-gradient(ellipse_80%_50%_at_20%_40%,rgba(0,229,199,0.1)_0%,transparent_50%),radial-gradient(ellipse_60%_40%_at_80%_60%,rgba(138,43,226,0.08)_0%,transparent_50%),radial-gradient(ellipse_50%_30%_at_50%_80%,rgba(0,150,255,0.07)_0%,transparent_50%)] animate-[pulse_10s_ease-in-out_infinite_alternate]" />
|
|
238
|
+
|
|
239
|
+
<canvas ref={canvasRef} className="fixed inset-0 w-full h-full opacity-60" />
|
|
240
|
+
|
|
241
|
+
<main className="relative z-10 w-full max-w-[900px] px-6 py-12 md:py-16 flex flex-col items-center gap-8 md:gap-12 text-center">
|
|
242
|
+
<header className="flex items-center gap-3 animate-fade-in opacity-0" style={{ animationDelay: '200ms' }}>
|
|
243
|
+
<span className="w-2 h-2 rounded-full bg-[#00e5c7] shadow-[0_0_12px_#00e5c7] animate-pulse" />
|
|
244
|
+
<code className="text-[10px] md:text-xs text-[#6b6b7a] tracking-widest font-mono uppercase">// ERROR: SYSTEM_VOID_0x19A</code>
|
|
245
|
+
</header>
|
|
246
|
+
|
|
247
|
+
<section className="flex flex-col items-center gap-6 md:gap-8">
|
|
248
|
+
<div className="flex gap-1 md:gap-2 perspective-[1000px] select-none">
|
|
249
|
+
{["4", "0", "4"].map((digit, i) => (
|
|
250
|
+
<span
|
|
251
|
+
key={i}
|
|
252
|
+
className="void-digit text-[clamp(6rem,20vw,14.5rem)] font-black italic tracking-tighter leading-none text-transparent bg-clip-text bg-gradient-to-b from-white to-white/20 drop-shadow-[0_10px_40px_rgba(255,255,255,0.1)] transition-transform duration-200"
|
|
253
|
+
style={{ transitionDelay: `${i * 100}ms` }}
|
|
254
|
+
>
|
|
255
|
+
{digit}
|
|
256
|
+
</span>
|
|
257
|
+
))}
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div className={cn("transition-all duration-500 transform", isChanging ? "opacity-0 translate-y-2" : "opacity-100 translate-y-0")}>
|
|
261
|
+
<h1 className="text-2xl md:text-5xl font-bold tracking-tight mb-4 flex flex-col items-center">
|
|
262
|
+
<span className="text-white/80">{messages[titleIdx].title[0]}</span>
|
|
263
|
+
<span className="text-[#00e5c7] drop-shadow-[0_0_15px_rgba(0,229,199,0.3)]">
|
|
264
|
+
{messages[titleIdx].title[1]}
|
|
265
|
+
</span>
|
|
266
|
+
</h1>
|
|
267
|
+
<p className="text-gray-400 text-base md:text-xl max-w-lg mx-auto leading-relaxed">
|
|
268
|
+
{messages[titleIdx].subtitle}
|
|
269
|
+
</p>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<div className="flex flex-col sm:flex-row justify-center gap-4 mt-4 w-full sm:w-auto px-4">
|
|
273
|
+
<Link
|
|
274
|
+
href="/"
|
|
275
|
+
className="px-8 py-4 rounded-2xl bg-white text-black font-bold hover:scale-105 active:scale-95 transition-all shadow-[0_20px_40px_rgba(255,255,255,0.1)] flex items-center justify-center gap-2 group"
|
|
276
|
+
>
|
|
277
|
+
Return Home
|
|
278
|
+
<span className="group-hover:translate-x-1 transition-transform">→</span>
|
|
279
|
+
</Link>
|
|
280
|
+
<button
|
|
281
|
+
onClick={() => history.back()}
|
|
282
|
+
className="px-8 py-4 rounded-2xl border border-white/10 bg-white/5 backdrop-blur-md text-white font-bold hover:bg-white/10 active:scale-95 transition-all flex items-center justify-center gap-2"
|
|
283
|
+
>
|
|
284
|
+
Back Track
|
|
285
|
+
</button>
|
|
286
|
+
<button
|
|
287
|
+
onClick={() => setOpen(true)}
|
|
288
|
+
className="px-8 py-4 rounded-2xl text-gray-500 font-bold hover:text-white transition-all flex items-center justify-center gap-2"
|
|
289
|
+
>
|
|
290
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
|
291
|
+
Search
|
|
292
|
+
</button>
|
|
293
|
+
</div>
|
|
294
|
+
</section>
|
|
295
|
+
</main>
|
|
296
|
+
|
|
297
|
+
{open && (
|
|
298
|
+
<div
|
|
299
|
+
className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/80 backdrop-blur-xl animate-fade-in"
|
|
300
|
+
onClick={() => setOpen(false)}
|
|
301
|
+
>
|
|
302
|
+
<div
|
|
303
|
+
className="w-full max-w-xl rounded-[2rem] border border-white/10 bg-[#10101a]/90 p-8 shadow-2xl overflow-hidden relative"
|
|
304
|
+
onClick={(e) => e.stopPropagation()}
|
|
305
|
+
>
|
|
306
|
+
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-[#00e5c7] to-transparent" />
|
|
307
|
+
<div className="flex items-center gap-4 mb-6">
|
|
308
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#00e5c7" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
|
309
|
+
<h2 className="text-xl font-bold">Search the Void</h2>
|
|
310
|
+
</div>
|
|
311
|
+
<input
|
|
312
|
+
onKeyDown={(e) => {
|
|
313
|
+
if (e.key === "Escape") setOpen(false);
|
|
314
|
+
if (e.key === "Enter") {
|
|
315
|
+
const v = (e.currentTarget as HTMLInputElement).value.trim();
|
|
316
|
+
if (v) window.location.href = `https://www.google.com/search?q=${encodeURIComponent(v)}`;
|
|
317
|
+
}
|
|
318
|
+
}}
|
|
319
|
+
placeholder="Type your coordinates..."
|
|
320
|
+
className="w-full bg-white/5 border border-white/10 rounded-xl py-4 px-6 outline-none font-mono text-lg text-white focus:border-[#00e5c7]/50 focus:bg-white/10 transition-all placeholder:text-gray-600"
|
|
321
|
+
/>
|
|
322
|
+
<div className="mt-6 flex items-center justify-between text-xs text-gray-500 font-mono">
|
|
323
|
+
<span className="flex items-center gap-2">
|
|
324
|
+
<kbd className="px-2 py-1 bg-white/5 rounded border border-white/10">ENTER</kbd>
|
|
325
|
+
to search
|
|
326
|
+
</span>
|
|
327
|
+
<span className="flex items-center gap-2">
|
|
328
|
+
<kbd className="px-2 py-1 bg-white/5 rounded border border-white/10">ESC</kbd>
|
|
329
|
+
to exit
|
|
330
|
+
</span>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
335
|
+
|
|
336
|
+
<div className="absolute bottom-8 left-8 text-[8px] md:text-[10px] text-gray-600 font-bold uppercase tracking-[0.3em] hidden sm:block">
|
|
337
|
+
Void Protocol Active // Stable
|
|
338
|
+
</div>
|
|
339
|
+
<div className="absolute bottom-8 right-8 text-[8px] md:text-[10px] text-gray-600 font-bold uppercase tracking-[0.3em] hidden sm:block text-right">
|
|
340
|
+
Coordinates: UNKNOWN
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|