404lab 1.0.1 → 2.0.2
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/commands/add.js +71 -0
- package/cli/commands/list.js +30 -0
- package/cli/commands/remove.js +31 -0
- package/cli/core/installer.js +156 -0
- package/cli/core/project.js +115 -0
- package/cli/core/templates.js +66 -0
- package/cli/index.js +119 -0
- package/cli/ui/colors.js +38 -0
- package/cli/ui/prompt.js +50 -0
- package/cli/ui/spinner.js +63 -0
- package/cli/utils/config.js +55 -0
- package/cli/utils/fs.js +69 -0
- package/package.json +5 -5
- package/templates/AmongUs.tsx +213 -119
- package/templates/BlueGlitch.tsx +190 -100
- package/templates/GeeksforGeeks.tsx +125 -37
- package/templates/Google.tsx +97 -29
- package/templates/MacOs.tsx +116 -212
- package/templates/ModernPage.tsx +73 -11
- package/templates/Particles.tsx +173 -67
- package/templates/Poet.tsx +139 -54
- package/templates/RetroTv.tsx +182 -130
- package/templates/SimplePage.tsx +60 -16
- package/templates/Snow.tsx +113 -179
- package/templates/StoneAge.tsx +91 -23
- package/templates/StrangerThings.tsx +79 -193
- package/templates/Terminal404.tsx +124 -242
- package/templates/Vercel.tsx +127 -0
- package/bin/index.js +0 -188
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { colors } from "./colors.js";
|
|
2
|
+
|
|
3
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
4
|
+
|
|
5
|
+
export function createSpinner(text) {
|
|
6
|
+
let frameIndex = 0;
|
|
7
|
+
let interval = null;
|
|
8
|
+
let currentText = text;
|
|
9
|
+
|
|
10
|
+
const clear = () => {
|
|
11
|
+
process.stdout.clearLine?.(0);
|
|
12
|
+
process.stdout.cursorTo?.(0);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const render = () => {
|
|
16
|
+
clear();
|
|
17
|
+
const frame = colors.cyan(frames[frameIndex]);
|
|
18
|
+
process.stdout.write(`${frame} ${currentText}`);
|
|
19
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
start(msg) {
|
|
24
|
+
if (msg) currentText = msg;
|
|
25
|
+
if (interval) return this;
|
|
26
|
+
interval = setInterval(render, 80);
|
|
27
|
+
render();
|
|
28
|
+
return this;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
update(msg) {
|
|
32
|
+
currentText = msg;
|
|
33
|
+
return this;
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
stop() {
|
|
37
|
+
if (interval) {
|
|
38
|
+
clearInterval(interval);
|
|
39
|
+
interval = null;
|
|
40
|
+
clear();
|
|
41
|
+
}
|
|
42
|
+
return this;
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
success(msg) {
|
|
46
|
+
this.stop();
|
|
47
|
+
console.log(`${colors.green("✔")} ${msg || currentText}`);
|
|
48
|
+
return this;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
fail(msg) {
|
|
52
|
+
this.stop();
|
|
53
|
+
console.log(`${colors.red("✖")} ${msg || currentText}`);
|
|
54
|
+
return this;
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
warn(msg) {
|
|
58
|
+
this.stop();
|
|
59
|
+
console.log(`${colors.yellow("⚠")} ${msg || currentText}`);
|
|
60
|
+
return this;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const CONFIG_FILE = "404lab.config.json";
|
|
5
|
+
|
|
6
|
+
export function getConfigPath(root = process.cwd()) {
|
|
7
|
+
return path.join(root, CONFIG_FILE);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function loadConfig(root = process.cwd()) {
|
|
11
|
+
const configPath = getConfigPath(root);
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(configPath)) {
|
|
14
|
+
return { installed: [] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
19
|
+
} catch {
|
|
20
|
+
return { installed: [] };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function saveConfig(config, root = process.cwd()) {
|
|
25
|
+
const configPath = getConfigPath(root);
|
|
26
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function addInstalledTemplate(templateName, root = process.cwd()) {
|
|
30
|
+
const config = loadConfig(root);
|
|
31
|
+
|
|
32
|
+
if (!config.installed.includes(templateName)) {
|
|
33
|
+
config.installed.push(templateName);
|
|
34
|
+
saveConfig(config, root);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return config;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function removeInstalledTemplate(templateName, root = process.cwd()) {
|
|
41
|
+
const config = loadConfig(root);
|
|
42
|
+
config.installed = config.installed.filter((t) => t !== templateName);
|
|
43
|
+
saveConfig(config, root);
|
|
44
|
+
return config;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isTemplateInstalled(templateName, root = process.cwd()) {
|
|
48
|
+
const config = loadConfig(root);
|
|
49
|
+
return config.installed.includes(templateName);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getInstalledTemplates(root = process.cwd()) {
|
|
53
|
+
const config = loadConfig(root);
|
|
54
|
+
return config.installed;
|
|
55
|
+
}
|
package/cli/utils/fs.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { log } from "../ui/colors.js";
|
|
4
|
+
import { confirm } from "../ui/prompt.js";
|
|
5
|
+
|
|
6
|
+
export function ensureDir(dirPath) {
|
|
7
|
+
if (!fs.existsSync(dirPath)) {
|
|
8
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function fileExists(filePath) {
|
|
13
|
+
return fs.existsSync(filePath);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function readFileContent(filePath) {
|
|
17
|
+
return fs.readFileSync(filePath, "utf8");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function writeFileContent(filePath, content) {
|
|
21
|
+
ensureDir(path.dirname(filePath));
|
|
22
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function deleteFile(filePath) {
|
|
26
|
+
if (fs.existsSync(filePath)) {
|
|
27
|
+
fs.unlinkSync(filePath);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function relativePath(from, to) {
|
|
34
|
+
return path.relative(from, to);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function safeWrite(filePath, content, options = {}) {
|
|
38
|
+
const { force = false, dry = false, root = process.cwd() } = options;
|
|
39
|
+
const relPath = path.relative(root, filePath);
|
|
40
|
+
|
|
41
|
+
if (dry) {
|
|
42
|
+
log.info(`Would create: ${relPath}`);
|
|
43
|
+
return { written: false, skipped: false, dry: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (fs.existsSync(filePath)) {
|
|
47
|
+
if (force) {
|
|
48
|
+
writeFileContent(filePath, content);
|
|
49
|
+
log.warn(`Overwritten: ${relPath}`);
|
|
50
|
+
return { written: true, skipped: false, overwritten: true };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
log.warn(`File exists: ${relPath}`);
|
|
54
|
+
const shouldOverwrite = await confirm("Overwrite?", true);
|
|
55
|
+
|
|
56
|
+
if (!shouldOverwrite) {
|
|
57
|
+
log.info(`Skipped: ${relPath}`);
|
|
58
|
+
return { written: false, skipped: true };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
writeFileContent(filePath, content);
|
|
62
|
+
log.success(`Overwritten: ${relPath}`);
|
|
63
|
+
return { written: true, skipped: false, overwritten: true };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
writeFileContent(filePath, content);
|
|
67
|
+
log.success(`Created: ${relPath}`);
|
|
68
|
+
return { written: true, skipped: false };
|
|
69
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "404lab",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "A CLI tool for generating beautiful custom 404 pages in Next.js projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"preferGlobal": true,
|
|
7
7
|
"bin": {
|
|
8
|
-
"404lab": "./
|
|
8
|
+
"404lab": "./cli/index.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
-
"
|
|
11
|
+
"cli",
|
|
12
12
|
"templates",
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
"generator",
|
|
21
21
|
"templates"
|
|
22
22
|
],
|
|
23
|
-
"author": "
|
|
23
|
+
"author": "Shreeteja Mutukundu <smutukundu2206@gmail.com>",
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"engines": {
|
|
26
26
|
"node": ">=16.0.0"
|
|
27
27
|
},
|
|
28
28
|
"repository": {
|
|
29
29
|
"type": "git",
|
|
30
|
-
"url": "https://github.com/
|
|
30
|
+
"url": "https://github.com/shreeteja172/404lab.git"
|
|
31
31
|
}
|
|
32
32
|
}
|
package/templates/AmongUs.tsx
CHANGED
|
@@ -1,152 +1,246 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useRef } from "react";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
4
|
import Image from "next/image";
|
|
5
5
|
import Link from "next/link";
|
|
6
|
+
import { motion } from "framer-motion";
|
|
7
|
+
import { cn } from "@/components/ui/cn";
|
|
6
8
|
|
|
7
|
-
const AmongUs = () => {
|
|
9
|
+
const AmongUs = ({ className }: { className?: string }) => {
|
|
8
10
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
11
|
+
const [stars, setStars] = useState<{ id: number; x: number; y: number; size: number; speed: number; opacity: number }[]>([]);
|
|
9
12
|
|
|
10
13
|
useEffect(() => {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"star pointer-events-none absolute w-[2px] h-[2px] bg-white animate-starTwinkle";
|
|
22
|
-
star.style.top = `${top}px`;
|
|
23
|
-
star.style.right = `${right}px`;
|
|
24
|
-
|
|
25
|
-
container.appendChild(star);
|
|
26
|
-
|
|
27
|
-
const runStar = setInterval(() => {
|
|
28
|
-
if (right >= width) {
|
|
29
|
-
star.remove();
|
|
30
|
-
clearInterval(runStar);
|
|
31
|
-
}
|
|
32
|
-
right += 3;
|
|
33
|
-
star.style.right = `${right}px`;
|
|
34
|
-
}, 10);
|
|
35
|
-
};
|
|
14
|
+
const initialStars = Array.from({ length: 100 }).map((_, i) => ({
|
|
15
|
+
id: i,
|
|
16
|
+
x: Math.random() * 100,
|
|
17
|
+
y: Math.random() * 100,
|
|
18
|
+
size: Math.random() * 2 + 0.5,
|
|
19
|
+
speed: Math.random() * 1.5 + 0.5,
|
|
20
|
+
opacity: Math.random() * 0.7 + 0.3,
|
|
21
|
+
}));
|
|
22
|
+
setStars(initialStars);
|
|
23
|
+
}, []);
|
|
36
24
|
|
|
37
|
-
|
|
25
|
+
const typewriterVariants = {
|
|
26
|
+
hidden: { opacity: 0 },
|
|
27
|
+
visible: {
|
|
28
|
+
opacity: 1,
|
|
29
|
+
transition: {
|
|
30
|
+
staggerChildren: 0.08,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
const letterVariants = {
|
|
36
|
+
hidden: { opacity: 0, y: 5 },
|
|
37
|
+
visible: { opacity: 1, y: 0 },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const text = "There is 1 Impostor among us";
|
|
44
41
|
|
|
45
42
|
return (
|
|
46
43
|
<div
|
|
47
44
|
ref={containerRef}
|
|
48
|
-
className=
|
|
45
|
+
className={cn(
|
|
46
|
+
"relative flex h-screen w-full flex-col items-center justify-center overflow-hidden bg-[#000000] text-white",
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
49
|
>
|
|
50
|
-
<div className="absolute top-[8%] text-center">
|
|
51
|
-
<p className="text-red-400 tracking-[0.3em] text-sm">
|
|
52
|
-
THERE IS 1 IMPOSTOR AMONG US
|
|
53
|
-
</p>
|
|
54
|
-
<h1 className="text-7xl font-extrabold mt-2">404</h1>
|
|
55
|
-
<p className="mt-2 opacity-80">This page was ejected into space.</p>
|
|
56
|
-
|
|
57
|
-
<Link
|
|
58
|
-
href="/"
|
|
59
|
-
className="inline-block mt-6 rounded-md bg-red-500 px-5 py-2 text-sm font-semibold hover:opacity-90 transition"
|
|
60
|
-
>
|
|
61
|
-
Return to Ship
|
|
62
|
-
</Link>
|
|
63
|
-
</div>
|
|
64
|
-
|
|
65
|
-
<Image
|
|
66
|
-
src="https://cdn.iconscout.com/icon/free/png-256/free-red-among-us-icon-svg-download-png-2691060.png"
|
|
67
|
-
alt="Impostor"
|
|
68
|
-
width={120}
|
|
69
|
-
height={120}
|
|
70
|
-
unoptimized
|
|
71
|
-
className="absolute top-[55%] w-24 h-auto animate-astronautFly"
|
|
72
|
-
/>
|
|
73
|
-
|
|
74
|
-
<Image
|
|
75
|
-
src="https://cdn.iconscout.com/icon/free/png-256/free-blue-among-us-icon-svg-download-png-2691064.png"
|
|
76
|
-
alt="Crewmate"
|
|
77
|
-
width={120}
|
|
78
|
-
height={120}
|
|
79
|
-
unoptimized
|
|
80
|
-
className="absolute bottom-[10%] left-[15%] w-24 h-auto animate-floatCrew"
|
|
81
|
-
/>
|
|
82
|
-
|
|
83
50
|
<style jsx global>{`
|
|
84
|
-
@
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
25% {
|
|
89
|
-
top: 52%;
|
|
90
|
-
transform: rotate(10deg);
|
|
91
|
-
}
|
|
92
|
-
50% {
|
|
93
|
-
top: 58%;
|
|
94
|
-
transform: rotate(20deg);
|
|
95
|
-
}
|
|
96
|
-
75% {
|
|
97
|
-
top: 62%;
|
|
98
|
-
transform: rotate(10deg);
|
|
99
|
-
}
|
|
100
|
-
100% {
|
|
101
|
-
left: 110%;
|
|
102
|
-
transform: rotate(20deg);
|
|
103
|
-
}
|
|
51
|
+
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
|
|
52
|
+
|
|
53
|
+
.among-us-font {
|
|
54
|
+
font-family: 'VT323', monospace;
|
|
104
55
|
}
|
|
105
56
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
50% {
|
|
111
|
-
transform: translateY(-12px);
|
|
112
|
-
}
|
|
113
|
-
100% {
|
|
114
|
-
transform: translateY(0px);
|
|
115
|
-
}
|
|
57
|
+
.star-field {
|
|
58
|
+
position: absolute;
|
|
59
|
+
inset: 0;
|
|
60
|
+
pointer-events: none;
|
|
116
61
|
}
|
|
117
62
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
25% {
|
|
123
|
-
background: rgba(255, 255, 255, 0.8);
|
|
124
|
-
}
|
|
125
|
-
50% {
|
|
126
|
-
background: rgba(255, 255, 255, 1);
|
|
127
|
-
}
|
|
128
|
-
75% {
|
|
129
|
-
background: rgba(255, 255, 255, 0.8);
|
|
130
|
-
}
|
|
131
|
-
100% {
|
|
132
|
-
background: rgba(255, 255, 255, 0.4);
|
|
133
|
-
}
|
|
63
|
+
.star {
|
|
64
|
+
position: absolute;
|
|
65
|
+
background: white;
|
|
66
|
+
border-radius: 50%;
|
|
134
67
|
}
|
|
135
68
|
|
|
136
|
-
.
|
|
137
|
-
|
|
69
|
+
.crt-overlay::before {
|
|
70
|
+
content: " ";
|
|
71
|
+
display: block;
|
|
72
|
+
position: absolute;
|
|
73
|
+
top: 0;
|
|
74
|
+
left: 0;
|
|
75
|
+
bottom: 0;
|
|
76
|
+
right: 0;
|
|
77
|
+
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
|
|
78
|
+
z-index: 30;
|
|
79
|
+
background-size: 100% 3px, 3px 100%;
|
|
80
|
+
pointer-events: none;
|
|
138
81
|
}
|
|
139
82
|
|
|
140
|
-
.
|
|
141
|
-
|
|
83
|
+
.crt-scanline {
|
|
84
|
+
width: 100%;
|
|
85
|
+
height: 100px;
|
|
86
|
+
z-index: 31;
|
|
87
|
+
background: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(255, 255, 255, 0.05) 10%, rgba(0, 0, 0, 0.1) 100%);
|
|
88
|
+
opacity: 0.1;
|
|
89
|
+
position: absolute;
|
|
90
|
+
bottom: 100%;
|
|
91
|
+
animation: scanline 8s linear infinite;
|
|
142
92
|
}
|
|
143
93
|
|
|
144
|
-
|
|
145
|
-
|
|
94
|
+
@keyframes scanline {
|
|
95
|
+
0% { bottom: 100%; }
|
|
96
|
+
100% { bottom: -100px; }
|
|
146
97
|
}
|
|
147
98
|
`}</style>
|
|
99
|
+
|
|
100
|
+
<div className="star-field">
|
|
101
|
+
{stars.map((star) => (
|
|
102
|
+
<motion.div
|
|
103
|
+
key={star.id}
|
|
104
|
+
className="star"
|
|
105
|
+
style={{
|
|
106
|
+
width: star.size,
|
|
107
|
+
height: star.size,
|
|
108
|
+
top: `${star.y}%`,
|
|
109
|
+
left: `${star.x}%`,
|
|
110
|
+
opacity: star.opacity,
|
|
111
|
+
boxShadow: `0 0 ${star.size * 2}px rgba(255, 255, 255, 0.5)`,
|
|
112
|
+
}}
|
|
113
|
+
animate={{
|
|
114
|
+
left: ["100%", "-10%"],
|
|
115
|
+
}}
|
|
116
|
+
transition={{
|
|
117
|
+
duration: 25 / star.speed,
|
|
118
|
+
repeat: Infinity,
|
|
119
|
+
ease: "linear",
|
|
120
|
+
delay: Math.random() * -25,
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<div className="z-10 flex flex-col items-center text-center px-4 among-us-font select-none">
|
|
127
|
+
<motion.h1
|
|
128
|
+
className="text-8xl md:text-[12rem] font-bold tracking-[0.1em] text-white mb-4 leading-none"
|
|
129
|
+
initial={{ opacity: 0, scale: 0.9 }}
|
|
130
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
131
|
+
transition={{ duration: 1.5, ease: "easeOut" }}
|
|
132
|
+
>
|
|
133
|
+
404
|
|
134
|
+
</motion.h1>
|
|
135
|
+
|
|
136
|
+
<motion.div
|
|
137
|
+
className="mb-8 h-12"
|
|
138
|
+
variants={typewriterVariants}
|
|
139
|
+
initial="hidden"
|
|
140
|
+
animate="visible"
|
|
141
|
+
>
|
|
142
|
+
<h2 className="text-2xl md:text-5xl text-red-500 uppercase font-black tracking-[0.2em]">
|
|
143
|
+
{text.split("").map((char, index) => (
|
|
144
|
+
<motion.span key={index} variants={letterVariants}>
|
|
145
|
+
{char}
|
|
146
|
+
</motion.span>
|
|
147
|
+
))}
|
|
148
|
+
</h2>
|
|
149
|
+
</motion.div>
|
|
150
|
+
|
|
151
|
+
<motion.div
|
|
152
|
+
className="space-y-6"
|
|
153
|
+
initial={{ opacity: 0 }}
|
|
154
|
+
animate={{ opacity: 1 }}
|
|
155
|
+
transition={{ delay: 3.5, duration: 1.2 }}
|
|
156
|
+
>
|
|
157
|
+
<p className="text-xl md:text-4xl text-gray-400 max-w-2xl mx-auto tracking-wide leading-relaxed">
|
|
158
|
+
This page was ejected into space.
|
|
159
|
+
</p>
|
|
160
|
+
|
|
161
|
+
<motion.p
|
|
162
|
+
className="text-lg md:text-3xl text-white tracking-widest"
|
|
163
|
+
initial={{ opacity: 0, y: 20 }}
|
|
164
|
+
animate={{ opacity: 1, y: 0 }}
|
|
165
|
+
transition={{ delay: 5, duration: 1 }}
|
|
166
|
+
>
|
|
167
|
+
<span className="text-red-600 font-bold px-2">404-Page</span> was not An Impostor.
|
|
168
|
+
</motion.p>
|
|
169
|
+
|
|
170
|
+
<motion.div
|
|
171
|
+
className="pt-12"
|
|
172
|
+
initial={{ opacity: 0 }}
|
|
173
|
+
animate={{ opacity: 1 }}
|
|
174
|
+
transition={{ delay: 6.5, duration: 1 }}
|
|
175
|
+
>
|
|
176
|
+
<Link
|
|
177
|
+
href="/"
|
|
178
|
+
className="group relative inline-flex items-center justify-center px-12 py-4 font-bold text-white transition-all duration-300 bg-transparent border-2 border-white/30 hover:border-white hover:bg-white hover:text-black rounded-sm uppercase tracking-[0.3em] text-xl"
|
|
179
|
+
>
|
|
180
|
+
Return to Ship
|
|
181
|
+
</Link>
|
|
182
|
+
</motion.div>
|
|
183
|
+
</motion.div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div className="absolute inset-0 pointer-events-none overflow-hidden">
|
|
187
|
+
<motion.div
|
|
188
|
+
className="absolute w-32 md:w-56 h-auto"
|
|
189
|
+
initial={{ left: "-25%", top: "40%", rotate: 0 }}
|
|
190
|
+
animate={{
|
|
191
|
+
left: "125%",
|
|
192
|
+
top: ["40%", "45%", "35%", "40%"],
|
|
193
|
+
rotate: 360
|
|
194
|
+
}}
|
|
195
|
+
transition={{
|
|
196
|
+
duration: 20,
|
|
197
|
+
repeat: Infinity,
|
|
198
|
+
ease: "linear"
|
|
199
|
+
}}
|
|
200
|
+
>
|
|
201
|
+
<Image
|
|
202
|
+
src="https://cdn.iconscout.com/icon/free/png-256/free-red-among-us-icon-svg-download-png-2691060.png"
|
|
203
|
+
alt="Ejected Red"
|
|
204
|
+
width={250}
|
|
205
|
+
height={250}
|
|
206
|
+
unoptimized
|
|
207
|
+
className="drop-shadow-[0_0_20px_rgba(255,0,0,0.5)]"
|
|
208
|
+
/>
|
|
209
|
+
</motion.div>
|
|
210
|
+
|
|
211
|
+
<motion.div
|
|
212
|
+
className="absolute w-24 md:w-40 h-auto"
|
|
213
|
+
initial={{ left: "-25%", top: "70%", rotate: 0 }}
|
|
214
|
+
animate={{
|
|
215
|
+
left: "125%",
|
|
216
|
+
top: ["70%", "55%", "65%", "70%"],
|
|
217
|
+
rotate: -360
|
|
218
|
+
}}
|
|
219
|
+
transition={{
|
|
220
|
+
duration: 28,
|
|
221
|
+
repeat: Infinity,
|
|
222
|
+
ease: "linear",
|
|
223
|
+
delay: 12
|
|
224
|
+
}}
|
|
225
|
+
>
|
|
226
|
+
<Image
|
|
227
|
+
src="https://cdn.iconscout.com/icon/free/png-256/free-blue-among-us-icon-svg-download-png-2691064.png"
|
|
228
|
+
alt="Ejected Blue"
|
|
229
|
+
width={200}
|
|
230
|
+
height={200}
|
|
231
|
+
unoptimized
|
|
232
|
+
className="drop-shadow-[0_0_20px_rgba(0,0,255,0.5)] opacity-60"
|
|
233
|
+
/>
|
|
234
|
+
</motion.div>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<div className="crt-overlay pointer-events-none absolute inset-0 z-40"></div>
|
|
238
|
+
<div className="crt-scanline z-50"></div>
|
|
239
|
+
<div className="absolute inset-0 pointer-events-none bg-[radial-gradient(circle_at_center,_transparent_0%,_rgba(0,0,0,0.7)_100%)] z-20"></div>
|
|
148
240
|
</div>
|
|
149
241
|
);
|
|
150
242
|
};
|
|
151
243
|
|
|
152
244
|
export default AmongUs;
|
|
245
|
+
|
|
246
|
+
|